From 3d83cf3841d9ec3a34ea8cb359701dbb3b10e355 Mon Sep 17 00:00:00 2001 From: "duc.phamhong" Date: Wed, 2 Oct 2024 17:50:33 +0700 Subject: [PATCH 1/5] Add source code for spine-runtimes --- CMakeLists.txt | 6 + .../Skylicht/SpineRuntimes/CMakeLists.txt | 70 + .../SpineRuntimes/CSkeletonDrawable.cpp | 42 + .../SpineRuntimes/CSkeletonDrawable.h | 41 + .../Skylicht/SpineRuntimes/CTextureLoader.cpp | 53 + .../Skylicht/SpineRuntimes/CTextureLoader.h | 43 + Projects/Skylicht/SpineRuntimes/pch.cpp | 6 + Projects/Skylicht/SpineRuntimes/pch.h | 28 + Projects/SpineCpp/CMakeLists.txt | 10 + Projects/SpineCpp/flags.cmake | 14 + .../spine-cpp/include/spine/Animation.h | 131 ++ .../spine-cpp/include/spine/AnimationState.h | 521 ++++++ .../include/spine/AnimationStateData.h | 90 + .../SpineCpp/spine-cpp/include/spine/Atlas.h | 139 ++ .../include/spine/AtlasAttachmentLoader.h | 72 + .../spine-cpp/include/spine/Attachment.h | 62 + .../include/spine/AttachmentLoader.h | 84 + .../include/spine/AttachmentTimeline.h | 82 + .../spine-cpp/include/spine/AttachmentType.h | 45 + .../spine-cpp/include/spine/BlendMode.h | 42 + .../spine-cpp/include/spine/BlockAllocator.h | 110 ++ .../SpineCpp/spine-cpp/include/spine/Bone.h | 286 +++ .../spine-cpp/include/spine/BoneData.h | 150 ++ .../include/spine/BoundingBoxAttachment.h | 54 + .../include/spine/ClippingAttachment.h | 65 + .../SpineCpp/spine-cpp/include/spine/Color.h | 110 ++ .../spine-cpp/include/spine/ColorTimeline.h | 197 ++ .../spine-cpp/include/spine/ConstraintData.h | 69 + .../spine-cpp/include/spine/ContainerUtil.h | 129 ++ .../spine-cpp/include/spine/CurveTimeline.h | 111 ++ .../SpineCpp/spine-cpp/include/spine/Debug.h | 139 ++ .../spine-cpp/include/spine/DeformTimeline.h | 80 + .../include/spine/DrawOrderTimeline.h | 61 + .../SpineCpp/spine-cpp/include/spine/Event.h | 86 + .../spine-cpp/include/spine/EventData.h | 86 + .../spine-cpp/include/spine/EventTimeline.h | 62 + .../spine-cpp/include/spine/Extension.h | 125 ++ .../include/spine/HasRendererObject.h | 65 + .../spine-cpp/include/spine/HashMap.h | 200 ++ .../spine-cpp/include/spine/IkConstraint.h | 118 ++ .../include/spine/IkConstraintData.h | 102 ++ .../include/spine/IkConstraintTimeline.h | 70 + .../spine-cpp/include/spine/Inherit.h | 43 + .../spine-cpp/include/spine/InheritTimeline.h | 71 + .../SpineCpp/spine-cpp/include/spine/Json.h | 116 ++ .../spine-cpp/include/spine/LinkedMesh.h | 61 + .../SpineCpp/spine-cpp/include/spine/Log.h | 57 + .../spine-cpp/include/spine/MathUtil.h | 139 ++ .../spine-cpp/include/spine/MeshAttachment.h | 122 ++ .../spine-cpp/include/spine/MixBlend.h | 45 + .../spine-cpp/include/spine/MixDirection.h | 44 + .../spine-cpp/include/spine/PathAttachment.h | 70 + .../spine-cpp/include/spine/PathConstraint.h | 133 ++ .../include/spine/PathConstraintData.h | 119 ++ .../include/spine/PathConstraintMixTimeline.h | 68 + .../spine/PathConstraintPositionTimeline.h | 64 + .../spine/PathConstraintSpacingTimeline.h | 59 + .../spine-cpp/include/spine/Physics.h | 49 + .../include/spine/PhysicsConstraint.h | 197 ++ .../include/spine/PhysicsConstraintData.h | 151 ++ .../include/spine/PhysicsConstraintTimeline.h | 288 +++ .../spine-cpp/include/spine/PointAttachment.h | 81 + .../SpineCpp/spine-cpp/include/spine/Pool.h | 74 + .../spine-cpp/include/spine/PositionMode.h | 40 + .../spine-cpp/include/spine/Property.h | 68 + .../SpineCpp/spine-cpp/include/spine/RTTI.h | 72 + .../include/spine/RegionAttachment.h | 140 ++ .../spine-cpp/include/spine/RotateMode.h | 41 + .../spine-cpp/include/spine/RotateTimeline.h | 61 + .../spine-cpp/include/spine/ScaleTimeline.h | 109 ++ .../spine-cpp/include/spine/Sequence.h | 98 + .../include/spine/SequenceTimeline.h | 73 + .../spine-cpp/include/spine/ShearTimeline.h | 109 ++ .../spine-cpp/include/spine/Skeleton.h | 285 +++ .../spine-cpp/include/spine/SkeletonBinary.h | 178 ++ .../spine-cpp/include/spine/SkeletonBounds.h | 121 ++ .../include/spine/SkeletonClipping.h | 88 + .../spine-cpp/include/spine/SkeletonData.h | 195 ++ .../spine-cpp/include/spine/SkeletonJson.h | 114 ++ .../include/spine/SkeletonRenderer.h | 68 + .../SpineCpp/spine-cpp/include/spine/Skin.h | 171 ++ .../SpineCpp/spine-cpp/include/spine/Slot.h | 137 ++ .../spine-cpp/include/spine/SlotData.h | 126 ++ .../spine-cpp/include/spine/SpacingMode.h | 42 + .../spine-cpp/include/spine/SpineObject.h | 59 + .../spine-cpp/include/spine/SpineString.h | 246 +++ .../spine-cpp/include/spine/TextureLoader.h | 51 + .../spine-cpp/include/spine/TextureRegion.h | 50 + .../spine-cpp/include/spine/Timeline.h | 86 + .../include/spine/TransformConstraint.h | 113 ++ .../include/spine/TransformConstraintData.h | 128 ++ .../spine/TransformConstraintTimeline.h | 71 + .../include/spine/TranslateTimeline.h | 113 ++ .../spine-cpp/include/spine/Triangulator.h | 70 + .../spine-cpp/include/spine/Updatable.h | 54 + .../SpineCpp/spine-cpp/include/spine/Vector.h | 222 +++ .../spine-cpp/include/spine/Version.h | 37 + .../include/spine/VertexAttachment.h | 101 ++ .../spine-cpp/include/spine/Vertices.h | 43 + .../SpineCpp/spine-cpp/include/spine/dll.h | 51 + .../SpineCpp/spine-cpp/include/spine/spine.h | 115 ++ .../spine-cpp/src/spine/Animation.cpp | 107 ++ .../spine-cpp/src/spine/AnimationState.cpp | 1098 +++++++++++ .../src/spine/AnimationStateData.cpp | 86 + .../SpineCpp/spine-cpp/src/spine/Atlas.cpp | 350 ++++ .../src/spine/AtlasAttachmentLoader.cpp | 112 ++ .../spine-cpp/src/spine/Attachment.cpp | 59 + .../spine-cpp/src/spine/AttachmentLoader.cpp | 48 + .../src/spine/AttachmentTimeline.cpp | 100 + .../SpineCpp/spine-cpp/src/spine/Bone.cpp | 595 ++++++ .../SpineCpp/spine-cpp/src/spine/BoneData.cpp | 166 ++ .../src/spine/BoundingBoxAttachment.cpp | 47 + .../src/spine/ClippingAttachment.cpp | 58 + .../spine-cpp/src/spine/ColorTimeline.cpp | 493 +++++ .../spine-cpp/src/spine/ConstraintData.cpp | 60 + .../spine-cpp/src/spine/CurveTimeline.cpp | 252 +++ .../spine-cpp/src/spine/DeformTimeline.cpp | 328 ++++ .../spine-cpp/src/spine/DrawOrderTimeline.cpp | 102 ++ .../SpineCpp/spine-cpp/src/spine/Event.cpp | 90 + .../spine-cpp/src/spine/EventData.cpp | 96 + .../spine-cpp/src/spine/EventTimeline.cpp | 99 + .../spine-cpp/src/spine/Extension.cpp | 127 ++ .../spine-cpp/src/spine/IkConstraint.cpp | 379 ++++ .../spine-cpp/src/spine/IkConstraintData.cpp | 107 ++ .../src/spine/IkConstraintTimeline.cpp | 141 ++ .../spine-cpp/src/spine/InheritTimeline.cpp | 81 + .../SpineCpp/spine-cpp/src/spine/Json.cpp | 559 ++++++ .../spine-cpp/src/spine/LinkedMesh.cpp | 52 + Projects/SpineCpp/spine-cpp/src/spine/Log.cpp | 123 ++ .../SpineCpp/spine-cpp/src/spine/MathUtil.cpp | 132 ++ .../spine-cpp/src/spine/MeshAttachment.cpp | 241 +++ .../spine-cpp/src/spine/PathAttachment.cpp | 71 + .../spine-cpp/src/spine/PathConstraint.cpp | 588 ++++++ .../src/spine/PathConstraintData.cpp | 136 ++ .../src/spine/PathConstraintMixTimeline.cpp | 126 ++ .../spine/PathConstraintPositionTimeline.cpp | 66 + .../spine/PathConstraintSpacingTimeline.cpp | 64 + .../spine-cpp/src/spine/PhysicsConstraint.cpp | 493 +++++ .../src/spine/PhysicsConstraintData.cpp | 223 +++ .../src/spine/PhysicsConstraintTimeline.cpp | 102 ++ .../spine-cpp/src/spine/PointAttachment.cpp | 88 + .../SpineCpp/spine-cpp/src/spine/RTTI.cpp | 56 + .../spine-cpp/src/spine/RegionAttachment.cpp | 275 +++ .../spine-cpp/src/spine/RotateTimeline.cpp | 59 + .../spine-cpp/src/spine/ScaleTimeline.cpp | 194 ++ .../SpineCpp/spine-cpp/src/spine/Sequence.cpp | 96 + .../spine-cpp/src/spine/SequenceTimeline.cpp | 124 ++ .../spine-cpp/src/spine/ShearTimeline.cpp | 162 ++ .../SpineCpp/spine-cpp/src/spine/Skeleton.cpp | 773 ++++++++ .../spine-cpp/src/spine/SkeletonBinary.cpp | 1490 +++++++++++++++ .../spine-cpp/src/spine/SkeletonBounds.cpp | 231 +++ .../spine-cpp/src/spine/SkeletonClipping.cpp | 397 ++++ .../spine-cpp/src/spine/SkeletonData.cpp | 244 +++ .../spine-cpp/src/spine/SkeletonJson.cpp | 1616 +++++++++++++++++ .../spine-cpp/src/spine/SkeletonRenderer.cpp | 246 +++ .../SpineCpp/spine-cpp/src/spine/Skin.cpp | 195 ++ .../SpineCpp/spine-cpp/src/spine/Slot.cpp | 129 ++ .../SpineCpp/spine-cpp/src/spine/SlotData.cpp | 99 + .../spine-cpp/src/spine/SpineObject.cpp | 63 + .../spine-cpp/src/spine/TextureLoader.cpp | 38 + .../SpineCpp/spine-cpp/src/spine/Timeline.cpp | 73 + .../src/spine/TransformConstraint.cpp | 350 ++++ .../src/spine/TransformConstraintData.cpp | 180 ++ .../src/spine/TransformConstraintTimeline.cpp | 157 ++ .../spine-cpp/src/spine/TranslateTimeline.cpp | 162 ++ .../spine-cpp/src/spine/Triangulator.cpp | 285 +++ .../spine-cpp/src/spine/Updatable.cpp | 40 + .../spine-cpp/src/spine/VertexAttachment.cpp | 167 ++ SkylichtConfig.cmake | 1 + 169 files changed, 26200 insertions(+) create mode 100644 Projects/Skylicht/SpineRuntimes/CMakeLists.txt create mode 100644 Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.cpp create mode 100644 Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h create mode 100644 Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp create mode 100644 Projects/Skylicht/SpineRuntimes/CTextureLoader.h create mode 100644 Projects/Skylicht/SpineRuntimes/pch.cpp create mode 100644 Projects/Skylicht/SpineRuntimes/pch.h create mode 100644 Projects/SpineCpp/CMakeLists.txt create mode 100644 Projects/SpineCpp/flags.cmake create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Animation.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/AnimationState.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/AnimationStateData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Atlas.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/AtlasAttachmentLoader.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Attachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/AttachmentLoader.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/AttachmentTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/AttachmentType.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/BlendMode.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/BlockAllocator.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Bone.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/BoneData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/BoundingBoxAttachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/ClippingAttachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Color.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/ColorTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/ConstraintData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/ContainerUtil.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/CurveTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Debug.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/DeformTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/DrawOrderTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Event.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/EventData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/EventTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Extension.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/HasRendererObject.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/HashMap.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/IkConstraint.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/IkConstraintData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/IkConstraintTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Inherit.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/InheritTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Json.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/LinkedMesh.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Log.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/MathUtil.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/MeshAttachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/MixBlend.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/MixDirection.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PathAttachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PathConstraint.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PathConstraintData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PathConstraintMixTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PathConstraintPositionTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PathConstraintSpacingTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Physics.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraint.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PointAttachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Pool.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/PositionMode.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Property.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/RTTI.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/RegionAttachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/RotateMode.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/RotateTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/ScaleTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Sequence.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SequenceTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/ShearTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Skeleton.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SkeletonBinary.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SkeletonBounds.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SkeletonClipping.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SkeletonData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SkeletonJson.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SkeletonRenderer.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Skin.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Slot.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SlotData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SpacingMode.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SpineObject.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/SpineString.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/TextureLoader.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/TextureRegion.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Timeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/TransformConstraint.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintData.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/TranslateTimeline.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Triangulator.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Updatable.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Vector.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Version.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/VertexAttachment.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/Vertices.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/dll.h create mode 100644 Projects/SpineCpp/spine-cpp/include/spine/spine.h create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Animation.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/AnimationState.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/AnimationStateData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Atlas.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/AtlasAttachmentLoader.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Attachment.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/AttachmentLoader.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/AttachmentTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Bone.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/BoneData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/BoundingBoxAttachment.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/ClippingAttachment.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/ColorTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/ConstraintData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/CurveTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/DeformTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/DrawOrderTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Event.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/EventData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/EventTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Extension.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/IkConstraint.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/IkConstraintData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/IkConstraintTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/InheritTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Json.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/LinkedMesh.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Log.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/MathUtil.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/MeshAttachment.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PathAttachment.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PathConstraint.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PathConstraintData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PathConstraintMixTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraint.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/PointAttachment.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/RTTI.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/RegionAttachment.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/RotateTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/ScaleTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Sequence.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SequenceTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/ShearTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Skeleton.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SkeletonBinary.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SkeletonBounds.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SkeletonClipping.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SkeletonData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SkeletonJson.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SkeletonRenderer.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Skin.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Slot.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SlotData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/SpineObject.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/TextureLoader.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Timeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/TransformConstraint.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintData.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/TranslateTimeline.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Triangulator.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/Updatable.cpp create mode 100644 Projects/SpineCpp/spine-cpp/src/spine/VertexAttachment.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ff782b5a2..55e4bf94a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -245,6 +245,12 @@ add_definitions(-DBUILD_IMGUI) subdirs (Projects/Imgui) endif() +# spine runtimes +if (BUILD_SPINE_RUNTIMES) +subdirs (Projects/SpineCpp) +subdirs (Projects/Skylicht/SpineRuntimes) +endif() + # skylicht components if (BUILD_SKYLICHT_COMPONENTS) add_definitions(-DBUILD_SKYLICHT_COMPONENTS) diff --git a/Projects/Skylicht/SpineRuntimes/CMakeLists.txt b/Projects/Skylicht/SpineRuntimes/CMakeLists.txt new file mode 100644 index 000000000..8bac2bda1 --- /dev/null +++ b/Projects/Skylicht/SpineRuntimes/CMakeLists.txt @@ -0,0 +1,70 @@ +include_directories( + ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp/spine-cpp/include + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/System + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes + ${SKYLICHT_ENGINE_PROJECT_DIR}/Irrlicht/Include + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Engine + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Components + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Collision + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Physics + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Lightmapper + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Audio + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/GUI +) + +if (BUILD_DEBUG_VLD) + set(vld_inc_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/include") + include_directories(${vld_inc_path}) + + if (CMAKE_CL_64) + set(vld_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/lib/Win64") + else() + set(vld_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/lib/Win32") + endif() +endif() + +if (BUILD_SKYLICHT_NETWORK) +include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Network) +endif() + +file(GLOB_RECURSE skylicht_spine_source + ./**.cpp + ./**.c + ./**.h) + +setup_project_group("${skylicht_spine_source}" ${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(SpineRuntimes ${ENGINE_SHARED_OR_STATIC_LIB} ${skylicht_spine_source}) + +if (BUILD_SHARED_LIBS) +set_target_properties(SpineRuntimes PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS true) +endif() + +target_precompiled_header(SpineRuntimes ./pch.cpp ${skylicht_spine_source}) + +set_target_properties(SpineRuntimes PROPERTIES VERSION ${SKYLICHT_VERSION}) + +target_link_libraries(SpineRuntimes Engine spine-cpp) + +if (BUILD_DEBUG_VLD) + target_link_libraries(SpineRuntimes vld) + target_link_directories(SpineRuntimes PUBLIC ${vld_lib_path}) +endif() + +if (INSTALL_LIBS) +install(TARGETS SpineRuntimes + EXPORT SpineRuntimesTargets + RUNTIME DESTINATION ${SKYLICHT_LIBRARY_INSTALL_DIR} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) + +install (DIRECTORY ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes + DESTINATION ${SKYLICHT_INCLUDE_INSTALL_DIR}/Skylicht + FILES_MATCHING PATTERN "*.h*") + +install(EXPORT SpineRuntimesTargets + FILE SpineRuntimesTargets.cmake + NAMESPACE Skylicht:: + DESTINATION ${SKYLICHT_LIBRARY_INSTALL_DIR}/cmake +) +endif() \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.cpp b/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.cpp new file mode 100644 index 000000000..45fa7d168 --- /dev/null +++ b/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.cpp @@ -0,0 +1,42 @@ +/* +!@ +MIT License + +Copyright (c) 2024 Skylicht Technology CO., LTD + +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. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#include "pch.h" +#include "CSkeletonDrawable.h" + +namespace Skylicht +{ + namespace Spine + { + CSkeletonDrawable::CSkeletonDrawable() + { + + } + + CSkeletonDrawable::~CSkeletonDrawable() + { + + } + } +} \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h b/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h new file mode 100644 index 000000000..e427a2e19 --- /dev/null +++ b/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h @@ -0,0 +1,41 @@ +/* +!@ +MIT License + +Copyright (c) 2024 Skylicht Technology CO., LTD + +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. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#pragma once + +#include + +namespace Skylicht +{ + namespace Spine + { + class CSkeletonDrawable + { + public: + CSkeletonDrawable(); + + virtual ~CSkeletonDrawable(); + }; + } +} \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp b/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp new file mode 100644 index 000000000..43f99dc1d --- /dev/null +++ b/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp @@ -0,0 +1,53 @@ +/* +!@ +MIT License + +Copyright (c) 2024 Skylicht Technology CO., LTD + +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. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#include "pch.h" +#include "CTextureLoader.h" + +#include "TextureManager/CTextureManager.h" + +namespace Skylicht +{ + namespace Spine + { + CTextureLoader::CTextureLoader() + { + + } + + void CTextureLoader::load(spine::AtlasPage& page, const spine::String& path) + { + ITexture* texture = CTextureManager::getInstance()->getTexture(path.buffer()); + if (!texture) + return; + page.texture = texture; + } + + void CTextureLoader::unload(void* texture) + { + ITexture* t = (ITexture*)texture; + CTextureManager::getInstance()->removeTexture(t); + } + } +} \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CTextureLoader.h b/Projects/Skylicht/SpineRuntimes/CTextureLoader.h new file mode 100644 index 000000000..407299d60 --- /dev/null +++ b/Projects/Skylicht/SpineRuntimes/CTextureLoader.h @@ -0,0 +1,43 @@ +/* +!@ +MIT License + +Copyright (c) 2024 Skylicht Technology CO., LTD + +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. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#pragma once + +#include + +namespace Skylicht +{ + namespace Spine + { + class CTextureLoader : public spine::TextureLoader + { + public: + CTextureLoader(); + + void load(spine::AtlasPage& page, const spine::String& path); + + void unload(void* texture); + }; + } +} \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/pch.cpp b/Projects/Skylicht/SpineRuntimes/pch.cpp new file mode 100644 index 000000000..01484ff5a --- /dev/null +++ b/Projects/Skylicht/SpineRuntimes/pch.cpp @@ -0,0 +1,6 @@ +// +// pch.cpp +// Include the standard header and generate the precompiled header. +// + +#include "pch.h" diff --git a/Projects/Skylicht/SpineRuntimes/pch.h b/Projects/Skylicht/SpineRuntimes/pch.h new file mode 100644 index 000000000..bd99ff3d1 --- /dev/null +++ b/Projects/Skylicht/SpineRuntimes/pch.h @@ -0,0 +1,28 @@ +/* +!@ +MIT License + +Copyright (c) 2019 Skylicht Technology CO., LTD + +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. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#pragma once + +#include "SkylichtConfig.h" +#include "SkylichtHeader.h" diff --git a/Projects/SpineCpp/CMakeLists.txt b/Projects/SpineCpp/CMakeLists.txt new file mode 100644 index 000000000..6dc5cb54e --- /dev/null +++ b/Projects/SpineCpp/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.10) + +include(flags.cmake) + +include_directories(include) +file(GLOB INCLUDES "spine-cpp/include/**/*.h") +file(GLOB SOURCES "spine-cpp/src/**/*.cpp") + +add_library(spine-cpp STATIC ${SOURCES} ${INCLUDES}) +target_include_directories(spine-cpp PUBLIC spine-cpp/include) \ No newline at end of file diff --git a/Projects/SpineCpp/flags.cmake b/Projects/SpineCpp/flags.cmake new file mode 100644 index 000000000..07ecfe486 --- /dev/null +++ b/Projects/SpineCpp/flags.cmake @@ -0,0 +1,14 @@ +option(SPINE_SANITIZE "Build with sanitization" OFF) + +if(MSVC) + message("MSCV detected") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS") +else() + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -std=c99") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wnon-virtual-dtor -pedantic -Wno-unused-parameter -std=c++11 -fno-exceptions -fno-rtti") + if (${SPINE_SANITIZE}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=undefined") + endif() +endif() \ No newline at end of file diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Animation.h b/Projects/SpineCpp/spine-cpp/include/spine/Animation.h new file mode 100644 index 000000000..f21f88eac --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Animation.h @@ -0,0 +1,131 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Animation_h +#define Spine_Animation_h + +#include +#include +#include +#include +#include +#include +#include + +namespace spine { + class Timeline; + + class Skeleton; + + class Event; + + class AnimationState; + + class SP_API Animation : public SpineObject { + friend class AnimationState; + + friend class TrackEntry; + + friend class AnimationStateData; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class TwoColorTimeline; + + public: + Animation(const String &name, Vector &timelines, float duration); + + ~Animation(); + + /// Applies all the animation's timelines to the specified skeleton. + /// See also Timeline::apply(Skeleton&, float, float, Vector, float, MixPose, MixDirection) + void apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction); + + const String &getName(); + + Vector &getTimelines(); + + bool hasTimeline(Vector &ids); + + float getDuration(); + + void setDuration(float inValue); + + /// @param target After the first and before the last entry. + static int search(Vector &values, float target); + + static int search(Vector &values, float target, int step); + private: + Vector _timelines; + HashMap _timelineIds; + float _duration; + String _name; + }; +} + +#endif /* Spine_Animation_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/AnimationState.h b/Projects/SpineCpp/spine-cpp/include/spine/AnimationState.h new file mode 100644 index 000000000..d130611db --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/AnimationState.h @@ -0,0 +1,521 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_AnimationState_h +#define Spine_AnimationState_h + +#include +#include +#include +#include +#include +#include +#include +#include "Slot.h" + +#ifdef SPINE_USE_STD_FUNCTION +#include +#endif + +namespace spine { + enum EventType { + EventType_Start = 0, + EventType_Interrupt, + EventType_End, + EventType_Complete, + EventType_Dispose, + EventType_Event + }; + + class AnimationState; + + class TrackEntry; + + class Animation; + + class Event; + + class AnimationStateData; + + class Skeleton; + + class RotateTimeline; + + class AttachmentTimeline; + +#ifdef SPINE_USE_STD_FUNCTION + typedef std::function AnimationStateListener; +#else + + typedef void (*AnimationStateListener)(AnimationState *state, EventType type, TrackEntry *entry, Event *event); + +#endif + + /// Abstract class to inherit from to create a callback object + class SP_API AnimationStateListenerObject { + public: + AnimationStateListenerObject() {}; + + virtual ~AnimationStateListenerObject() {}; + public: + /// The callback function to be called + virtual void callback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) = 0; + }; + + /// State for the playback of an animation + class SP_API TrackEntry : public SpineObject, public HasRendererObject { + friend class EventQueue; + + friend class AnimationState; + + public: + TrackEntry(); + + virtual ~TrackEntry(); + + /// The index of the track where this entry is either current or queued. + int getTrackIndex(); + + /// The animation to apply for this track entry. + Animation *getAnimation(); + + TrackEntry *getPrevious(); + + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + bool getLoop(); + + void setLoop(bool inValue); + + /// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead + /// of being mixed out. + /// + /// When mixing between animations that key the same property, if a lower track also keys that property then the value will + /// briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% + /// while the second animation mixes from 0% to 100%. Setting holdPrevious to true applies the first animation + /// at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which + /// keys the property, only when a higher track also keys the property. + /// + /// Snapping will occur if holdPrevious is true and this animation does not key all the same properties as the + /// previous animation. + bool getHoldPrevious(); + + void setHoldPrevious(bool inValue); + + bool getReverse(); + + void setReverse(bool inValue); + + bool getShortestRotation(); + + void setShortestRotation(bool inValue); + + /// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing + /// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the + /// track entry will become the current track entry. + float getDelay(); + + void setDelay(float inValue); + + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// TrackEntry.AnimationTime. The track time can be set to start the animation at a time other than 0, without affecting looping. + float getTrackTime(); + + void setTrackTime(float inValue); + + /// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for + /// non-looping animations and to int.MaxValue for looping animations. If the track end time is reached and no + /// other animations are queued for playback, and mixing from any previous animations is complete, properties keyed by the animation, + /// are set to the setup pose and the track is cleared. + /// + /// It may be desired to use AnimationState.addEmptyAnimation(int, float, float) to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + float getTrackEnd(); + + void setTrackEnd(float inValue); + + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the animation start time, it often makes sense to set TrackEntry.AnimationLast to the same value to + /// prevent timeline keys before the start time from triggering. + float getAnimationStart(); + + void setAnimationStart(float inValue); + + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to TrackEntry.AnimationStart at this time. Defaults to the animation duration. + float getAnimationEnd(); + + void setAnimationEnd(float inValue); + + /// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this + /// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time + /// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied. + float getAnimationLast(); + + void setAnimationLast(float inValue); + + /// Uses TrackEntry.TrackTime to compute the animation time between TrackEntry.AnimationStart. and + /// TrackEntry.AnimationEnd. When the track time is 0, the animation time is equal to the animation start time. + float getAnimationTime(); + + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + float getTimeScale(); + + void setTimeScale(float inValue); + + /// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with + /// this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense + /// to use alpha on track 0 if the skeleton pose is from the last frame render. + float getAlpha(); + + void setAlpha(float inValue); + + /// + /// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation + /// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out. + float getEventThreshold(); + + void setEventThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the + /// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being + /// mixed out. + float getMixAttachmentThreshold(); + + void setMixAttachmentThreshold(float inValue); + + /// When getAlpha() is greater than alphaAttachmentThreshold, attachment timelines are applied. + /// Defaults to 0, so attachment timelines are always applied. */ + float getAlphaAttachmentThreshold(); + + void setAlphaAttachmentThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the + /// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being + /// mixed out. + float getMixDrawOrderThreshold(); + + void setMixDrawOrderThreshold(float inValue); + + /// The animation queued to start after this animation, or NULL. + TrackEntry *getNext(); + + /// Returns true if at least one loop has been completed. + bool isComplete(); + + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// TrackEntry.MixDuration when the mix is complete. + float getMixTime(); + + void setMixTime(float inValue); + + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// AnimationStateData based on the animation before this animation (if any). + /// + /// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix. + /// In that case, the mixDuration must be set before AnimationState.update(float) is next called. + /// + /// When using AnimationState::addAnimation(int, Animation, bool, float) with a delay + /// less than or equal to 0, note the Delay is set using the mix duration from the AnimationStateData + float getMixDuration(); + + void setMixDuration(float inValue); + + void setMixDuration(float mixDuration, float delay); + + MixBlend getMixBlend(); + + void setMixBlend(MixBlend blend); + + /// The track entry for the previous animation when mixing from the previous animation to this animation, or NULL if no + /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a double linked list with MixingTo. + TrackEntry *getMixingFrom(); + + /// The track entry for the next animation when mixing from this animation, or NULL if no mixing is currently occuring. + /// When mixing from multiple animations, MixingTo makes up a double linked list with MixingFrom. + TrackEntry *getMixingTo(); + + /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the + /// long way around when using alpha and starting animations on other tracks. + /// + /// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. + /// The two rotations likely change over time, so which direction is the short or long way also changes. + /// If the short way was always chosen, bones would flip to the other side when that direction became the long way. + /// TrackEntry chooses the short way the first time it is applied and remembers that direction. + void resetRotationDirections(); + + float getTrackComplete(); + + void setListener(AnimationStateListener listener); + + void setListener(AnimationStateListenerObject *listener); + + /// Returns true if this track entry has been applied at least once. + /// + /// See AnimationState::apply(Skeleton). + bool wasApplied(); + + /// Returns true if there is a getNext() track entry that is ready to become the current track entry during the + /// next AnimationState::update(float)} + bool isNextReady () { + return _next != NULL && _nextTrackLast - _next->_delay >= 0; + } + + private: + Animation *_animation; + TrackEntry *_previous; + TrackEntry *_next; + TrackEntry *_mixingFrom; + TrackEntry *_mixingTo; + int _trackIndex; + + bool _loop, _holdPrevious, _reverse, _shortestRotation; + float _eventThreshold, _mixAttachmentThreshold, _alphaAttachmentThreshold, _mixDrawOrderThreshold; + float _animationStart, _animationEnd, _animationLast, _nextAnimationLast; + float _delay, _trackTime, _trackLast, _nextTrackLast, _trackEnd, _timeScale; + float _alpha, _mixTime, _mixDuration, _interruptAlpha, _totalAlpha; + MixBlend _mixBlend; + Vector _timelineMode; + Vector _timelineHoldMix; + Vector _timelinesRotation; + AnimationStateListener _listener; + AnimationStateListenerObject *_listenerObject; + + void reset(); + }; + + class SP_API EventQueueEntry : public SpineObject { + friend class EventQueue; + + public: + EventType _type; + TrackEntry *_entry; + Event *_event; + + EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event = NULL); + }; + + class SP_API EventQueue : public SpineObject { + friend class AnimationState; + + private: + Vector _eventQueueEntries; + AnimationState &_state; + bool _drainDisabled; + + static EventQueue *newEventQueue(AnimationState &state); + + static EventQueueEntry newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event = NULL); + + EventQueue(AnimationState &state); + + ~EventQueue(); + + void start(TrackEntry *entry); + + void interrupt(TrackEntry *entry); + + void end(TrackEntry *entry); + + void dispose(TrackEntry *entry); + + void complete(TrackEntry *entry); + + void event(TrackEntry *entry, Event *event); + + /// Raises all events in the queue and drains the queue. + void drain(); + }; + + class SP_API AnimationState : public SpineObject, public HasRendererObject { + friend class TrackEntry; + + friend class EventQueue; + + public: + explicit AnimationState(AnimationStateData *data); + + ~AnimationState(); + + /// Increments the track entry times, setting queued animations as current if needed + /// @param delta delta time + void update(float delta); + + /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the + /// animation state can be applied to multiple skeletons to pose them identically. + bool apply(Skeleton &skeleton); + + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTracks(); + + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTrack(size_t trackIndex); + + /// Sets an animation by name. setAnimation(int, Animation, bool) + TrackEntry *setAnimation(size_t trackIndex, const String &animationName, bool loop); + + /// Sets the current animation for a track, discarding any queued animations. + /// @param loop If true, the animation will repeat. + /// If false, it will not, instead its last frame is applied if played beyond its duration. + /// In either case TrackEntry.TrackEnd determines when the track is cleared. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose. + TrackEntry *setAnimation(size_t trackIndex, Animation *animation, bool loop); + + /// Queues an animation by name. + /// addAnimation(int, Animation, bool, float) + TrackEntry *addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay); + + /// Adds an animation to be played delay seconds after the current or last queued animation + /// for a track. If the track is empty, it is equivalent to calling setAnimation. + /// @param delay + /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + /// + /// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose + TrackEntry *addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay); + + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + TrackEntry *setEmptyAnimation(size_t trackIndex, float mixDuration); + + /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the + /// specified mix duration. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose. + /// + /// @param trackIndex Track number. + /// @param mixDuration Mix duration. + /// @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + TrackEntry *addEmptyAnimation(size_t trackIndex, float mixDuration, float delay); + + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + void setEmptyAnimations(float mixDuration); + + /// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing. + TrackEntry *getCurrent(size_t trackIndex); + + AnimationStateData *getData(); + + /// A list of tracks that have animations, which may contain NULLs. + Vector &getTracks(); + + float getTimeScale(); + + void setTimeScale(float inValue); + + void setListener(AnimationStateListener listener); + + void setListener(AnimationStateListenerObject *listener); + + void disableQueue(); + + void enableQueue(); + + void setManualTrackEntryDisposal(bool inValue); + + bool getManualTrackEntryDisposal(); + + void disposeTrackEntry(TrackEntry *entry); + + private: + static const int Subsequent = 0; + static const int First = 1; + static const int HoldSubsequent = 2; + static const int HoldFirst = 3; + static const int HoldMix = 4; + + static const int Setup = 1; + static const int Current = 2; + + AnimationStateData *_data; + + Pool _trackEntryPool; + Vector _tracks; + Vector _events; + EventQueue *_queue; + + HashMap _propertyIDs; + bool _animationsChanged; + + AnimationStateListener _listener; + AnimationStateListenerObject *_listenerObject; + + int _unkeyedState; + + float _timeScale; + + bool _manualTrackEntryDisposal; + + static Animation *getEmptyAnimation(); + + static void + applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, MixBlend pose, + Vector &timelinesRotation, size_t i, bool firstFrame); + + void applyAttachmentTimeline(AttachmentTimeline *attachmentTimeline, Skeleton &skeleton, float animationTime, + MixBlend pose, bool firstFrame); + + /// Returns true when all mixing from entries are complete. + bool updateMixingFrom(TrackEntry *to, float delta); + + float applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBlend currentPose); + + void queueEvents(TrackEntry *entry, float animationTime); + + /// Sets the active TrackEntry for a given track number. + void setCurrent(size_t index, TrackEntry *current, bool interrupt); + + /// Removes the next entry and all entries after it for the specified entry. */ + void clearNext(TrackEntry *entry); + + TrackEntry *expandToIndex(size_t index); + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// @param last May be NULL. + TrackEntry *newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last); + + void animationsChanged(); + + void computeHold(TrackEntry *entry); + + void setAttachment(Skeleton &skeleton, spine::Slot &slot, const String &attachmentName, bool attachments); + }; +} + +#endif /* Spine_AnimationState_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/AnimationStateData.h b/Projects/SpineCpp/spine-cpp/include/spine/AnimationStateData.h new file mode 100644 index 000000000..bdde48e5f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/AnimationStateData.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_AnimationStateData_h +#define Spine_AnimationStateData_h + +#include +#include +#include + +#include + +namespace spine { + class SkeletonData; + + class Animation; + + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + class SP_API AnimationStateData : public SpineObject { + friend class AnimationState; + + public: + explicit AnimationStateData(SkeletonData *skeletonData); + + /// The SkeletonData to look up animations when they are specified by name. + SkeletonData *getSkeletonData(); + + /// The mix duration to use when no mix duration has been specifically defined between two animations. + float getDefaultMix(); + + void setDefaultMix(float inValue); + + /// Sets a mix duration by animation names. + void setMix(const String &fromName, const String &toName, float duration); + + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + void setMix(Animation *from, Animation *to, float duration); + + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + float getMix(Animation *from, Animation *to); + + /// Removes all mixes and sets the default mix to 0. + void clear(); + + private: + class AnimationPair : public SpineObject { + public: + Animation *_a1; + Animation *_a2; + + explicit AnimationPair(Animation *a1 = NULL, Animation *a2 = NULL); + + bool operator==(const AnimationPair &other) const; + }; + + SkeletonData *_skeletonData; + float _defaultMix; + HashMap _animationToMixTime; + }; +} + +#endif /* Spine_AnimationStateData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Atlas.h b/Projects/SpineCpp/spine-cpp/include/spine/Atlas.h new file mode 100644 index 000000000..822467e24 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Atlas.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Atlas_h +#define Spine_Atlas_h + +#include +#include +#include +#include +#include +#include "TextureRegion.h" + +namespace spine { + enum Format { + Format_Alpha, + Format_Intensity, + Format_LuminanceAlpha, + Format_RGB565, + Format_RGBA4444, + Format_RGB888, + Format_RGBA8888 + }; + + // Our TextureFilter collides with UE4's TextureFilter in unity builds. We rename + // TextureFilter to SpineTextureFilter in UE4. +#ifdef SPINE_UE4 + #define TEXTURE_FILTER_ENUM SpineTextureFilter +#else + #define TEXTURE_FILTER_ENUM TextureFilter +#endif + + enum TEXTURE_FILTER_ENUM { + TextureFilter_Unknown, + TextureFilter_Nearest, + TextureFilter_Linear, + TextureFilter_MipMap, + TextureFilter_MipMapNearestNearest, + TextureFilter_MipMapLinearNearest, + TextureFilter_MipMapNearestLinear, + TextureFilter_MipMapLinearLinear + }; + + enum TextureWrap { + TextureWrap_MirroredRepeat, + TextureWrap_ClampToEdge, + TextureWrap_Repeat + }; + + class SP_API AtlasPage : public SpineObject { + public: + String name; + String texturePath; + Format format; + TEXTURE_FILTER_ENUM minFilter; + TEXTURE_FILTER_ENUM magFilter; + TextureWrap uWrap; + TextureWrap vWrap; + int width, height; + bool pma; + int index; + void *texture; + + explicit AtlasPage(const String &inName) : name(inName), format(Format_RGBA8888), + minFilter(TextureFilter_Nearest), + magFilter(TextureFilter_Nearest), uWrap(TextureWrap_ClampToEdge), + vWrap(TextureWrap_ClampToEdge), width(0), height(0), pma(false), index(0), texture(NULL) { + } + }; + + class SP_API AtlasRegion : public TextureRegion { + public: + AtlasPage *page; + String name; + int index; + int x, y; + Vector splits; + Vector pads; + Vector names; + Vector values; + }; + + class TextureLoader; + + class SP_API Atlas : public SpineObject { + public: + Atlas(const String &path, TextureLoader *textureLoader, bool createTexture = true); + + Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture = true); + + ~Atlas(); + + void flipV(); + + /// Returns the first region found with the specified name. This method uses String comparison to find the region, so the result + /// should be cached rather than calling this method multiple times. + /// @return The region, or NULL. + AtlasRegion *findRegion(const String &name); + + Vector &getPages(); + + Vector &getRegions(); + + private: + Vector _pages; + Vector _regions; + TextureLoader *_textureLoader; + + void load(const char *begin, int length, const char *dir, bool createTexture); + }; +} + +#endif /* Spine_Atlas_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/AtlasAttachmentLoader.h b/Projects/SpineCpp/spine-cpp/include/spine/AtlasAttachmentLoader.h new file mode 100644 index 000000000..d45ebafc4 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/AtlasAttachmentLoader.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_AtlasAttachmentLoader_h +#define Spine_AtlasAttachmentLoader_h + +#include +#include +#include + + +namespace spine { + class Atlas; + + class AtlasRegion; + + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data about Loading Skeleton Data in the Spine Runtimes Guide. + class SP_API AtlasAttachmentLoader : public AttachmentLoader { + public: + RTTI_DECL + + explicit AtlasAttachmentLoader(Atlas *atlas); + + virtual RegionAttachment *newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence); + + virtual MeshAttachment *newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence); + + virtual BoundingBoxAttachment *newBoundingBoxAttachment(Skin &skin, const String &name); + + virtual PathAttachment *newPathAttachment(Skin &skin, const String &name); + + virtual PointAttachment *newPointAttachment(Skin &skin, const String &name); + + virtual ClippingAttachment *newClippingAttachment(Skin &skin, const String &name); + + virtual void configureAttachment(Attachment *attachment); + + AtlasRegion *findRegion(const String &name); + + private: + Atlas *_atlas; + }; +} + +#endif /* Spine_AtlasAttachmentLoader_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Attachment.h b/Projects/SpineCpp/spine-cpp/include/spine/Attachment.h new file mode 100644 index 000000000..418716773 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Attachment.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Attachment_h +#define Spine_Attachment_h + +#include +#include +#include + +namespace spine { + class SP_API Attachment : public SpineObject { + RTTI_DECL + + public: + explicit Attachment(const String &name); + + virtual ~Attachment(); + + const String &getName() const; + + virtual Attachment *copy() = 0; + + int getRefCount(); + + void reference(); + + void dereference(); + + private: + const String _name; + int _refCount; + }; +} + +#endif /* Spine_Attachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/AttachmentLoader.h b/Projects/SpineCpp/spine-cpp/include/spine/AttachmentLoader.h new file mode 100644 index 000000000..66c0e364d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/AttachmentLoader.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_AttachmentLoader_h +#define Spine_AttachmentLoader_h + +#include +#include +#include + +namespace spine { + class Skin; + + class Attachment; + + class RegionAttachment; + + class MeshAttachment; + + class BoundingBoxAttachment; + + class PathAttachment; + + class PointAttachment; + + class ClippingAttachment; + + class Sequence; + + class SP_API AttachmentLoader : public SpineObject { + public: + RTTI_DECL + + AttachmentLoader(); + + virtual ~AttachmentLoader(); + + /// @return May be NULL to not load any attachment. + virtual RegionAttachment *newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) = 0; + + /// @return May be NULL to not load any attachment. + virtual MeshAttachment *newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) = 0; + + /// @return May be NULL to not load any attachment. + virtual BoundingBoxAttachment *newBoundingBoxAttachment(Skin &skin, const String &name) = 0; + + /// @return May be NULL to not load any attachment + virtual PathAttachment *newPathAttachment(Skin &skin, const String &name) = 0; + + virtual PointAttachment *newPointAttachment(Skin &skin, const String &name) = 0; + + virtual ClippingAttachment *newClippingAttachment(Skin &skin, const String &name) = 0; + + virtual void configureAttachment(Attachment *attachment) = 0; + }; +} + +#endif /* Spine_AttachmentLoader_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/AttachmentTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/AttachmentTimeline.h new file mode 100644 index 000000000..6f3b98469 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/AttachmentTimeline.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_AttachmentTimeline_h +#define Spine_AttachmentTimeline_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + + class Skeleton; + + class Slot; + + class Event; + + class SP_API AttachmentTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit AttachmentTimeline(size_t frameCount, int slotIndex); + + virtual ~AttachmentTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, const String &attachmentName); + + Vector &getAttachmentNames(); + + int getSlotIndex() { return _slotIndex; } + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + Vector _attachmentNames; + + void setAttachment(Skeleton &skeleton, Slot &slot, String *attachmentName); + }; +} + +#endif /* Spine_AttachmentTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/AttachmentType.h b/Projects/SpineCpp/spine-cpp/include/spine/AttachmentType.h new file mode 100644 index 000000000..a92b60776 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/AttachmentType.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_AttachmentType_h +#define Spine_AttachmentType_h + +namespace spine { + enum AttachmentType { + AttachmentType_Region, + AttachmentType_Boundingbox, + AttachmentType_Mesh, + AttachmentType_Linkedmesh, + AttachmentType_Path, + AttachmentType_Point, + AttachmentType_Clipping + }; +} + +#endif /* Spine_AttachmentType_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/BlendMode.h b/Projects/SpineCpp/spine-cpp/include/spine/BlendMode.h new file mode 100644 index 000000000..989a1be41 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/BlendMode.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_BlendMode_h +#define Spine_BlendMode_h + +namespace spine { + enum BlendMode { + BlendMode_Normal = 0, + BlendMode_Additive, + BlendMode_Multiply, + BlendMode_Screen + }; +} + +#endif /* Spine_BlendMode_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/BlockAllocator.h b/Projects/SpineCpp/spine-cpp/include/spine/BlockAllocator.h new file mode 100644 index 000000000..422d2a487 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/BlockAllocator.h @@ -0,0 +1,110 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ +#ifndef Spine_BlockAllocator_h +#define Spine_BlockAllocator_h + +#include +#include +#include +#include +#include + +namespace spine { + struct Block { + int size; + int allocated; + uint8_t *memory; + + int free() { + return size - allocated; + } + + bool canFit(int numBytes) { + return free() >= numBytes; + } + + uint8_t *allocate(int numBytes) { + uint8_t *ptr = memory + allocated; + allocated += numBytes; + return ptr; + } + }; + + class BlockAllocator : public SpineObject { + int initialBlockSize; + Vector blocks; + + public: + BlockAllocator(int initialBlockSize) : initialBlockSize(initialBlockSize) { + blocks.add(newBlock(initialBlockSize)); + } + + ~BlockAllocator() { + for (int i = 0, n = (int) blocks.size(); i < n; i++) { + SpineExtension::free(blocks[i].memory, __FILE__, __LINE__); + } + } + + template + T *allocate(size_t num) { + return (T *) _allocate((int) (sizeof(T) * num)); + } + + void compress() { + if (blocks.size() == 1) return; + int totalSize = 0; + for (int i = 0, n = (int)blocks.size(); i < n; i++) { + totalSize += blocks[i].size; + SpineExtension::free(blocks[i].memory, __FILE__, __LINE__); + } + blocks.clear(); + blocks.add(newBlock(totalSize)); + } + + private: + void *_allocate(int numBytes) { + // 16-byte align allocations + int alignedNumBytes = numBytes + (numBytes % 16 != 0 ? 16 - (numBytes % 16) : 0); + Block *block = &blocks[blocks.size() - 1]; + if (!block->canFit(alignedNumBytes)) { + blocks.add(newBlock(MathUtil::max(initialBlockSize, alignedNumBytes))); + block = &blocks[blocks.size() - 1]; + } + return block->allocate(alignedNumBytes); + } + + Block newBlock(int numBytes) { + Block block = {MathUtil::max(initialBlockSize, numBytes), 0, nullptr}; + block.memory = SpineExtension::alloc(block.size, __FILE__, __LINE__); + return block; + } + }; +} + +#endif \ No newline at end of file diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Bone.h b/Projects/SpineCpp/spine-cpp/include/spine/Bone.h new file mode 100644 index 000000000..1af0aeda6 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Bone.h @@ -0,0 +1,286 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Bone_h +#define Spine_Bone_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class Skeleton; + +/// Stores a bone's current pose. +/// +/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a +/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a +/// constraint or application code modifies the world transform after it was computed from the local transform. + class SP_API Bone : public Updatable { + friend class AnimationState; + + friend class RotateTimeline; + + friend class IkConstraint; + + friend class TransformConstraint; + + friend class VertexAttachment; + + friend class PathConstraint; + + friend class PhysicsConstraint; + + friend class Skeleton; + + friend class RegionAttachment; + + friend class PointAttachment; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class InheritTimeline; + + RTTI_DECL + + public: + static void setYDown(bool inValue); + + static bool isYDown(); + + /// @param parent May be NULL. + Bone(BoneData &data, Skeleton &skeleton, Bone *parent = NULL); + + /// Same as updateWorldTransform. This method exists for Bone to implement Spine::Updatable. + virtual void update(Physics physics); + + /// Computes the world transform using the parent bone and this bone's local transform. + void updateWorldTransform(); + + /// Computes the world transform using the parent bone and the specified local transform. + void + updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY); + + /// Computes the individual applied transform values from the world transform. This can be useful to perform processing using + /// the applied transform after the world transform has been modified directly (eg, by a constraint).. + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. + void updateAppliedTransform(); + + void setToSetupPose(); + + void worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY); + + void worldToParent(float worldX, float worldY, float &outParentX, float &outParentY); + + void localToWorld(float localX, float localY, float &outWorldX, float &outWorldY); + + void parentToWorld(float worldX, float worldY, float &outX, float &outY); + + float worldToLocalRotation(float worldRotation); + + float localToWorldRotation(float localRotation); + + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// @param degrees Degrees. + void rotateWorld(float degrees); + + float getWorldToLocalRotationX(); + + float getWorldToLocalRotationY(); + + BoneData &getData(); + + Skeleton &getSkeleton(); + + Bone *getParent(); + + Vector &getChildren(); + + /// The local X translation. + float getX(); + + void setX(float inValue); + + /// The local Y translation. + float getY(); + + void setY(float inValue); + + /// The local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// The local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// The local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// The local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// The local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The rotation, as calculated by any constraints. + float getAppliedRotation(); + + void setAppliedRotation(float inValue); + + /// The applied local x translation. + float getAX(); + + void setAX(float inValue); + + /// The applied local y translation. + float getAY(); + + void setAY(float inValue); + + /// The applied local scaleX. + float getAScaleX(); + + void setAScaleX(float inValue); + + /// The applied local scaleY. + float getAScaleY(); + + void setAScaleY(float inValue); + + /// The applied local shearX. + float getAShearX(); + + void setAShearX(float inValue); + + /// The applied local shearY. + float getAShearY(); + + void setAShearY(float inValue); + + float getA(); + + void setA(float inValue); + + float getB(); + + void setB(float inValue); + + float getC(); + + void setC(float inValue); + + float getD(); + + void setD(float inValue); + + float getWorldX(); + + void setWorldX(float inValue); + + float getWorldY(); + + void setWorldY(float inValue); + + float getWorldRotationX(); + + float getWorldRotationY(); + + /// Returns the magnitide (always positive) of the world scale X. + float getWorldScaleX(); + + /// Returns the magnitide (always positive) of the world scale Y. + float getWorldScaleY(); + + bool isActive(); + + void setActive(bool inValue); + + Inherit getInherit() { return _inherit; } + + void setInherit(Inherit inValue) { _inherit = inValue; } + + private: + static bool yDown; + + BoneData &_data; + Skeleton &_skeleton; + Bone *_parent; + Vector _children; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + float _ax, _ay, _arotation, _ascaleX, _ascaleY, _ashearX, _ashearY; + float _a, _b, _worldX; + float _c, _d, _worldY; + bool _sorted; + bool _active; + Inherit _inherit; + }; +} + +#endif /* Spine_Bone_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/BoneData.h b/Projects/SpineCpp/spine-cpp/include/spine/BoneData.h new file mode 100644 index 000000000..8e7a29370 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/BoneData.h @@ -0,0 +1,150 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_BoneData_h +#define Spine_BoneData_h + +#include +#include +#include +#include + +namespace spine { + class SP_API BoneData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + public: + BoneData(int index, const String &name, BoneData *parent = NULL); + + /// The index of the bone in Skeleton.Bones + int getIndex(); + + /// The name of the bone, which is unique within the skeleton. + const String &getName(); + + /// May be NULL. + BoneData *getParent(); + + float getLength(); + + void setLength(float inValue); + + /// Local X translation. + float getX(); + + void setX(float inValue); + + /// Local Y translation. + float getY(); + + void setY(float inValue); + + /// Local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// Local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// Local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// Local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// Local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The transform mode for how parent world transforms affect this bone. + Inherit getInherit(); + + void setInherit(Inherit inValue); + + bool isSkinRequired(); + + void setSkinRequired(bool inValue); + + Color &getColor(); + + const String &getIcon(); + + void setIcon(const String &icon); + + bool isVisible(); + + void setVisible(bool inValue); + + private: + const int _index; + const String _name; + BoneData *_parent; + float _length; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + Inherit _inherit; + bool _skinRequired; + Color _color; + String _icon; + bool _visible; + }; +} + +#endif /* Spine_BoneData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/BoundingBoxAttachment.h b/Projects/SpineCpp/spine-cpp/include/spine/BoundingBoxAttachment.h new file mode 100644 index 000000000..0ea3e2a71 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/BoundingBoxAttachment.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_BoundingBoxAttachment_h +#define Spine_BoundingBoxAttachment_h + +#include +#include +#include + +namespace spine { + /// Attachment that has a polygon for bounds checking. + class SP_API BoundingBoxAttachment : public VertexAttachment { + RTTI_DECL + + public: + explicit BoundingBoxAttachment(const String &name); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + Color _color; + }; +} + +#endif /* Spine_BoundingBoxAttachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/ClippingAttachment.h b/Projects/SpineCpp/spine-cpp/include/spine/ClippingAttachment.h new file mode 100644 index 000000000..ce7675eb8 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/ClippingAttachment.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_ClippingAttachment_h +#define Spine_ClippingAttachment_h + +#include +#include + +namespace spine { + class SlotData; + + class SP_API ClippingAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class SkeletonClipping; + + RTTI_DECL + + public: + explicit ClippingAttachment(const String &name); + + SlotData *getEndSlot(); + + void setEndSlot(SlotData *inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + SlotData *_endSlot; + Color _color; + }; +} + +#endif /* Spine_ClippingAttachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Color.h b/Projects/SpineCpp/spine-cpp/include/spine/Color.h new file mode 100644 index 000000000..676f2cd4d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Color.h @@ -0,0 +1,110 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef SPINE_COLOR_H +#define SPINE_COLOR_H + +#include + +namespace spine { + class SP_API Color : public SpineObject { + public: + Color() : r(0), g(0), b(0), a(0) { + } + + Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) { + clamp(); + } + + inline Color &set(float _r, float _g, float _b, float _a) { + this->r = _r; + this->g = _g; + this->b = _b; + this->a = _a; + clamp(); + return *this; + } + + inline Color &set(float _r, float _g, float _b) { + this->r = _r; + this->g = _g; + this->b = _b; + clamp(); + return *this; + } + + inline Color &set(const Color &other) { + r = other.r; + g = other.g; + b = other.b; + a = other.a; + clamp(); + return *this; + } + + inline Color &add(float _r, float _g, float _b, float _a) { + this->r += _r; + this->g += _g; + this->b += _b; + this->a += _a; + clamp(); + return *this; + } + + inline Color &add(float _r, float _g, float _b) { + this->r += _r; + this->g += _g; + this->b += _b; + clamp(); + return *this; + } + + inline Color &add(const Color &other) { + r += other.r; + g += other.g; + b += other.b; + a += other.a; + clamp(); + return *this; + } + + inline Color &clamp() { + r = MathUtil::clamp(this->r, 0, 1); + g = MathUtil::clamp(this->g, 0, 1); + b = MathUtil::clamp(this->b, 0, 1); + a = MathUtil::clamp(this->a, 0, 1); + return *this; + } + + float r, g, b, a; + }; +} + + +#endif //SPINE_COLOR_H diff --git a/Projects/SpineCpp/spine-cpp/include/spine/ColorTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/ColorTimeline.h new file mode 100644 index 000000000..e2528f106 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/ColorTimeline.h @@ -0,0 +1,197 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_ColorTimeline_h +#define Spine_ColorTimeline_h + +#include + +namespace spine { + class SP_API RGBATimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBATimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBATimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float a); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 5; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int A = 4; + }; + + class SP_API RGBTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBTimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 4; + static const int R = 1; + static const int G = 2; + static const int B = 3; + }; + + class SP_API AlphaTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit AlphaTimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~AlphaTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + }; + + class SP_API RGBA2Timeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBA2Timeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBA2Timeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 8; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int A = 4; + static const int R2 = 5; + static const int G2 = 6; + static const int B2 = 7; + }; + + class SP_API RGB2Timeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGB2Timeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGB2Timeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float r2, float g2, float b2); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 7; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int R2 = 4; + static const int G2 = 5; + static const int B2 = 6; + }; +} + +#endif /* Spine_ColorTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/ConstraintData.h b/Projects/SpineCpp/spine-cpp/include/spine/ConstraintData.h new file mode 100644 index 000000000..3ed15f32b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/ConstraintData.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Constraint_h +#define Spine_Constraint_h + +#include +#include + +namespace spine { + /// The interface for all constraints. + class SP_API ConstraintData : public SpineObject { + + friend class SkeletonBinary; + + RTTI_DECL + + public: + ConstraintData(const String &name); + + virtual ~ConstraintData(); + + /// The IK constraint's name, which is unique within the skeleton. + const String &getName(); + + /// The ordinal for the order a skeleton's constraints will be applied. + size_t getOrder(); + + void setOrder(size_t inValue); + + /// Whether the constraint is only active for a specific skin. + bool isSkinRequired(); + + void setSkinRequired(bool inValue); + + private: + const String _name; + size_t _order; + bool _skinRequired; + }; +} + +#endif /* Spine_Constraint_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/ContainerUtil.h b/Projects/SpineCpp/spine-cpp/include/spine/ContainerUtil.h new file mode 100644 index 000000000..3e2212821 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/ContainerUtil.h @@ -0,0 +1,129 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_ContainerUtil_h +#define Spine_ContainerUtil_h + +#include +#include +#include +#include +#include + +#include + +namespace spine { + class SP_API ContainerUtil : public SpineObject { + public: + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T *findWithName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T *item = items[i]; + if (item->getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T *item = items[i]; + if (item->getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T *findWithDataName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T *item = items[i]; + if (item->getData().getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithDataName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T *item = items[i]; + if (item->getData().getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + template + static void cleanUpVectorOfPointers(Vector &items) { + for (int i = (int) items.size() - 1; i >= 0; i--) { + T *item = items[i]; + + delete item; + + items.removeAt(i); + } + } + + private: + // ctor, copy ctor, and assignment should be private in a Singleton + ContainerUtil(); + + ContainerUtil(const ContainerUtil &); + + ContainerUtil &operator=(const ContainerUtil &); + }; +} + +#endif /* Spine_ContainerUtil_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/CurveTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/CurveTimeline.h new file mode 100644 index 000000000..a8a2257ad --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/CurveTimeline.h @@ -0,0 +1,111 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_CurveTimeline_h +#define Spine_CurveTimeline_h + +#include +#include + +namespace spine { + /// Base class for frames that use an interpolation bezier curve. + class SP_API CurveTimeline : public Timeline { + RTTI_DECL + + public: + explicit CurveTimeline(size_t frameCount, size_t frameEntries, size_t bezierCount); + + virtual ~CurveTimeline(); + + void setLinear(size_t frame); + + void setStepped(size_t frame); + + virtual void + setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2); + + float getBezierValue(float time, size_t frame, size_t valueOffset, size_t i); + + Vector &getCurves(); + + protected: + static const int LINEAR = 0; + static const int STEPPED = 1; + static const int BEZIER = 2; + static const int BEZIER_SIZE = 18; + + Vector _curves; // type, x, y, ... + }; + + class SP_API CurveTimeline1 : public CurveTimeline { + RTTI_DECL + + public: + explicit CurveTimeline1(size_t frameCount, size_t bezierCount); + + virtual ~CurveTimeline1(); + + void setFrame(size_t frame, float time, float value); + + float getCurveValue(float time); + + float getRelativeValue(float time, float alpha, MixBlend blend, float current, float setup); + + float getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup); + + float getAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup, float value); + + float getScaleValue (float time, float alpha, MixBlend blend, MixDirection direction, float current, float setup); + + protected: + static const int ENTRIES = 2; + static const int VALUE = 1; + }; + + class SP_API CurveTimeline2 : public CurveTimeline { + RTTI_DECL + + public: + explicit CurveTimeline2(size_t frameCount, size_t bezierCount); + + virtual ~CurveTimeline2(); + + void setFrame(size_t frame, float time, float value1, float value2); + + float getCurveValue(float time); + + protected: + static const int ENTRIES = 3; + static const int VALUE1 = 1; + static const int VALUE2 = 2; + }; +} + +#endif /* Spine_CurveTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Debug.h b/Projects/SpineCpp/spine-cpp/include/spine/Debug.h new file mode 100644 index 000000000..1c5baec96 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Debug.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef SPINE_LOG_H +#define SPINE_LOG_H + +#include +#include + +#include + +namespace spine { + + class SP_API DebugExtension : public SpineExtension { + struct Allocation { + void *address; + size_t size; + const char *fileName; + int line; + + Allocation() : address(NULL), size(0), fileName(NULL), line(0) { + } + + Allocation(void *a, size_t s, const char *f, int l) : address(a), size(s), fileName(f), line(l) { + } + }; + + public: + DebugExtension(SpineExtension *extension) : _extension(extension), _allocations(0), _reallocations(0), + _frees(0) { + } + + void reportLeaks() { + for (std::map::iterator it = _allocated.begin(); it != _allocated.end(); it++) { + printf("\"%s:%i (%zu bytes at %p)\n", it->second.fileName, it->second.line, it->second.size, + it->second.address); + } + printf("allocations: %zu, reallocations: %zu, frees: %zu\n", _allocations, _reallocations, _frees); + if (_allocated.empty()) printf("No leaks detected\n"); + } + + void clearAllocations() { + _allocated.clear(); + _usedMemory = 0; + } + + virtual void *_alloc(size_t size, const char *file, int line) { + void *result = _extension->_alloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_calloc(size_t size, const char *file, int line) { + void *result = _extension->_calloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) { + if (_allocated.count(ptr)) _usedMemory -= _allocated[ptr].size; + _allocated.erase(ptr); + void *result = _extension->_realloc(ptr, size, file, line); + _reallocations++; + _allocated[result] = Allocation(result, size, file, line); + _usedMemory += size; + return result; + } + + virtual void _free(void *mem, const char *file, int line) { + if (_allocated.count(mem)) { + _extension->_free(mem, file, line); + _frees++; + _usedMemory -= _allocated[mem].size; + _allocated.erase(mem); + return; + } + + printf("%s:%i (address %p): Double free or not allocated through SpineExtension\n", file, line, mem); + _extension->_free(mem, file, line); + } + + virtual char *_readFile(const String &path, int *length) { + auto data = _extension->_readFile(path, length); + + if (_allocated.count(data) == 0) { + _allocated[data] = Allocation(data, sizeof(char) * (*length), nullptr, 0); + _allocations++; + _usedMemory += sizeof(char) * (*length); + } + + return data; + } + + size_t getUsedMemory() { + return _usedMemory; + } + + private: + SpineExtension *_extension; + std::map _allocated; + size_t _allocations; + size_t _reallocations; + size_t _frees; + size_t _usedMemory; + }; +} + + +#endif //SPINE_LOG_H diff --git a/Projects/SpineCpp/spine-cpp/include/spine/DeformTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/DeformTimeline.h new file mode 100644 index 000000000..6b8555d1d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/DeformTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_DeformTimeline_h +#define Spine_DeformTimeline_h + +#include + +namespace spine { + class VertexAttachment; + + class SP_API DeformTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit DeformTimeline(size_t frameCount, size_t bezierCount, int slotIndex, VertexAttachment *attachment); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, Vector &vertices); + + Vector > &getVertices(); + + VertexAttachment *getAttachment(); + + void setAttachment(VertexAttachment *inValue); + + virtual void + setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2); + + float getCurvePercent(float time, int frame); + + int getSlotIndex() { return _slotIndex; } + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + Vector > _vertices; + + VertexAttachment *_attachment; + }; +} + +#endif /* Spine_DeformTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/DrawOrderTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/DrawOrderTimeline.h new file mode 100644 index 000000000..873fe7561 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/DrawOrderTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_DrawOrderTimeline_h +#define Spine_DrawOrderTimeline_h + +#include + +namespace spine { + class SP_API DrawOrderTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit DrawOrderTimeline(size_t frameCount); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + /// @param drawOrder May be NULL to use bind pose draw order + void setFrame(size_t frame, float time, Vector &drawOrder); + + Vector > &getDrawOrders(); + + private: + Vector > _drawOrders; + }; +} + +#endif /* Spine_DrawOrderTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Event.h b/Projects/SpineCpp/spine-cpp/include/spine/Event.h new file mode 100644 index 000000000..ac0b12246 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Event.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Event_h +#define Spine_Event_h + +#include +#include + +namespace spine { + class EventData; + +/// Stores the current pose values for an Event. + class SP_API Event : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + public: + Event(float time, const EventData &data); + + const EventData &getData(); + + /// The animation time this event was keyed. + float getTime(); + + int getIntValue(); + + void setIntValue(int inValue); + + float getFloatValue(); + + void setFloatValue(float inValue); + + const String &getStringValue(); + + void setStringValue(const String &inValue); + + float getVolume(); + + void setVolume(float inValue); + + float getBalance(); + + void setBalance(float inValue); + + private: + const EventData &_data; + const float _time; + int _intValue; + float _floatValue; + String _stringValue; + float _volume; + float _balance; + }; +} + +#endif /* Spine_Event_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/EventData.h b/Projects/SpineCpp/spine-cpp/include/spine/EventData.h new file mode 100644 index 000000000..5099c565b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/EventData.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_EventData_h +#define Spine_EventData_h + +#include +#include + +namespace spine { +/// Stores the setup pose values for an Event. + class SP_API EventData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Event; + + public: + explicit EventData(const String &name); + + /// The name of the event, which is unique within the skeleton. + const String &getName() const; + + int getIntValue() const; + + void setIntValue(int inValue); + + float getFloatValue() const; + + void setFloatValue(float inValue); + + const String &getStringValue() const; + + void setStringValue(const String &inValue); + + const String &getAudioPath() const; + + void setAudioPath(const String &inValue); + + float getVolume() const; + + void setVolume(float inValue); + + float getBalance() const; + + void setBalance(float inValue); + + private: + const String _name; + int _intValue; + float _floatValue; + String _stringValue; + String _audioPath; + float _volume; + float _balance; + }; +} + +#endif /* Spine_EventData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/EventTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/EventTimeline.h new file mode 100644 index 000000000..e9a89be62 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/EventTimeline.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_EventTimeline_h +#define Spine_EventTimeline_h + +#include + +namespace spine { + class SP_API EventTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit EventTimeline(size_t frameCount); + + ~EventTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(size_t frame, Event *event); + + Vector &getEvents(); + + private: + Vector _events; + }; +} + +#endif /* Spine_EventTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Extension.h b/Projects/SpineCpp/spine-cpp/include/spine/Extension.h new file mode 100644 index 000000000..9757ea7a5 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Extension.h @@ -0,0 +1,125 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Extension_h +#define Spine_Extension_h + + +#include +#include + +#define SP_UNUSED(x) (void)(x) + +namespace spine { + class String; + + class SP_API SpineExtension { + public: + template + static T *alloc(size_t num, const char *file, int line) { + return (T *) getInstance()->_alloc(sizeof(T) * num, file, line); + } + + template + static T *calloc(size_t num, const char *file, int line) { + return (T *) getInstance()->_calloc(sizeof(T) * num, file, line); + } + + template + static T *realloc(T *ptr, size_t num, const char *file, int line) { + return (T *) getInstance()->_realloc(ptr, sizeof(T) * num, file, line); + } + + template + static void free(T *ptr, const char *file, int line) { + getInstance()->_free((void *) ptr, file, line); + } + + template + static void beforeFree(T *ptr) { + getInstance()->_beforeFree((void *) ptr); + } + + static char *readFile(const String &path, int *length) { + return getInstance()->_readFile(path, length); + } + + static void setInstance(SpineExtension *inSpineExtension); + + static SpineExtension *getInstance(); + + virtual ~SpineExtension(); + + /// Implement this function to use your own memory allocator + virtual void *_alloc(size_t size, const char *file, int line) = 0; + + virtual void *_calloc(size_t size, const char *file, int line) = 0; + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) = 0; + + /// If you provide a spineAllocFunc, you should also provide a spineFreeFunc + virtual void _free(void *mem, const char *file, int line) = 0; + + virtual char *_readFile(const String &path, int *length) = 0; + + virtual void _beforeFree(void *ptr) { SP_UNUSED(ptr); } + + protected: + SpineExtension(); + + private: + static SpineExtension *_instance; + }; + + class SP_API DefaultSpineExtension : public SpineExtension { + public: + DefaultSpineExtension(); + + virtual ~DefaultSpineExtension(); + + protected: + virtual void *_alloc(size_t size, const char *file, int line) override; + + virtual void *_calloc(size_t size, const char *file, int line) override; + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) override; + + virtual void _free(void *mem, const char *file, int line) override; + + virtual char *_readFile(const String &path, int *length) override; + }; + +// This function is to be implemented by engine specific runtimes to provide +// the default extension for that engine. It is called the first time +// SpineExtension::getInstance() is called, when no instance has been set +// yet. + extern SpineExtension *getDefaultExtension(); +} + +#endif /* Spine_Extension_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/HasRendererObject.h b/Projects/SpineCpp/spine-cpp/include/spine/HasRendererObject.h new file mode 100644 index 000000000..579293928 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/HasRendererObject.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_HasRendererObject_h +#define Spine_HasRendererObject_h + +#include + +namespace spine { + + typedef void (*DisposeRendererObject)(void *rendererObject); + + class SP_API HasRendererObject { + public: + explicit HasRendererObject() : _rendererObject(0), _dispose(0) {}; + + virtual ~HasRendererObject() { + if (_dispose && _rendererObject) + _dispose(_rendererObject); + } + + void *getRendererObject() { return _rendererObject; } + + void setRendererObject(void *rendererObject, DisposeRendererObject dispose = 0) { + if (_dispose && _rendererObject && _rendererObject != rendererObject) + _dispose(_rendererObject); + + _rendererObject = rendererObject; + _dispose = dispose; + } + + private: + void *_rendererObject; + DisposeRendererObject _dispose; + }; + +} + +#endif diff --git a/Projects/SpineCpp/spine-cpp/include/spine/HashMap.h b/Projects/SpineCpp/spine-cpp/include/spine/HashMap.h new file mode 100644 index 000000000..741425cd7 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/HashMap.h @@ -0,0 +1,200 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_HashMap_h +#define Spine_HashMap_h + +#include +#include + +// Required for new with line number and file name in MSVC +#ifdef _MSC_VER +#pragma warning(disable:4291) + +#pragma warning(disable:4251) + +#endif + +namespace spine { + template + class SP_API HashMap : public SpineObject { + private: + class Entry; + + public: + class SP_API Pair { + public: + explicit Pair(K &k, V &v) : key(k), value(v) {} + + K &key; + V &value; + }; + + class SP_API Entries { + public: + friend class HashMap; + + explicit Entries(Entry *entry) : _entry(NULL), _hasChecked(false) { + _start.next = entry; + _entry = &_start; + } + + Pair next() { + assert(_entry); + assert(_hasChecked); + _entry = _entry->next; + Pair pair(_entry->_key, _entry->_value); + _hasChecked = false; + return pair; + } + + bool hasNext() { + _hasChecked = true; + return _entry->next; + } + + private: + bool _hasChecked; + Entry _start; + Entry *_entry; + }; + + HashMap() : + _head(NULL), + _size(0) { + } + + ~HashMap() { + clear(); + } + + void clear() { + for (Entry *entry = _head; entry != NULL;) { + Entry *next = entry->next; + delete entry; + entry = next; + } + _head = NULL; + _size = 0; + } + + size_t size() { + return _size; + } + + void put(const K &key, const V &value) { + Entry *entry = find(key); + if (entry) { + entry->_key = key; + entry->_value = value; + } else { + entry = new(__FILE__, __LINE__) Entry(); + entry->_key = key; + entry->_value = value; + + Entry *oldHead = _head; + + if (oldHead) { + _head = entry; + oldHead->prev = entry; + entry->next = oldHead; + } else { + _head = entry; + } + _size++; + } + } + + bool addAll(Vector &keys, const V &value) { + size_t oldSize = _size; + for (size_t i = 0; i < keys.size(); i++) { + put(keys[i], value); + } + return _size != oldSize; + } + + bool containsKey(const K &key) { + return find(key) != NULL; + } + + bool remove(const K &key) { + Entry *entry = find(key); + if (!entry) return false; + + Entry *prev = entry->prev; + Entry *next = entry->next; + + if (prev) prev->next = next; + else _head = next; + if (next) next->prev = entry->prev; + + delete entry; + _size--; + + return true; + } + + V operator[](const K &key) { + Entry *entry = find(key); + if (entry) return entry->_value; + else { + assert(false); + return 0; + } + } + + Entries getEntries() const { + return Entries(_head); + } + + private: + Entry *find(const K &key) { + for (Entry *entry = _head; entry != NULL; entry = entry->next) { + if (entry->_key == key) + return entry; + } + return NULL; + } + + class SP_API Entry : public SpineObject { + public: + K _key; + V _value; + Entry *next; + Entry *prev; + + Entry() : next(NULL), prev(NULL) {} + }; + + Entry *_head; + size_t _size; + }; +} + +#endif /* Spine_HashMap_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/IkConstraint.h b/Projects/SpineCpp/spine-cpp/include/spine/IkConstraint.h new file mode 100644 index 000000000..a43043f24 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/IkConstraint.h @@ -0,0 +1,118 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_IkConstraint_h +#define Spine_IkConstraint_h + +#include + +#include + +namespace spine { + class IkConstraintData; + + class Skeleton; + + class Bone; + + class SP_API IkConstraint : public Updatable { + friend class Skeleton; + + friend class IkConstraintTimeline; + + RTTI_DECL + + public: + /// Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified + /// in the world coordinate system. + static void + apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha); + + /// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as + /// possible. The target is specified in the world coordinate system. + /// @param child A direct descendant of the parent bone. + static void + apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, + float alpha); + + IkConstraint(IkConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + IkConstraintData &getData(); + + Vector &getBones(); + + Bone *getTarget(); + + void setTarget(Bone *inValue); + + int getBendDirection(); + + void setBendDirection(int inValue); + + bool getCompress(); + + void setCompress(bool inValue); + + bool getStretch(); + + void setStretch(bool inValue); + + float getMix(); + + void setMix(float inValue); + + float getSoftness(); + + void setSoftness(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + IkConstraintData &_data; + Vector _bones; + int _bendDirection; + bool _compress; + bool _stretch; + float _mix; + float _softness; + Bone *_target; + bool _active; + }; +} + +#endif /* Spine_IkConstraint_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/IkConstraintData.h b/Projects/SpineCpp/spine-cpp/include/spine/IkConstraintData.h new file mode 100644 index 000000000..96f59131b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/IkConstraintData.h @@ -0,0 +1,102 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_IkConstraintData_h +#define Spine_IkConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API IkConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class IkConstraint; + + friend class Skeleton; + + friend class IkConstraintTimeline; + + public: + RTTI_DECL + + explicit IkConstraintData(const String &name); + + /// The bones that are constrained by this IK Constraint. + Vector &getBones(); + + /// The bone that is the IK target. + BoneData *getTarget(); + + void setTarget(BoneData *inValue); + + /// Controls the bend direction of the IK bones, either 1 or -1. + int getBendDirection(); + + void setBendDirection(int inValue); + + bool getCompress(); + + void setCompress(bool inValue); + + bool getStretch(); + + void setStretch(bool inValue); + + bool getUniform(); + + void setUniform(bool inValue); + + float getMix(); + + void setMix(float inValue); + + float getSoftness(); + + void setSoftness(float inValue); + + private: + Vector _bones; + BoneData *_target; + int _bendDirection; + bool _compress; + bool _stretch; + bool _uniform; + float _mix; + float _softness; + }; +} + +#endif /* Spine_IkConstraintData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/IkConstraintTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/IkConstraintTimeline.h new file mode 100644 index 000000000..4a3426f4b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/IkConstraintTimeline.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_IkConstraintTimeline_h +#define Spine_IkConstraintTimeline_h + +#include + +namespace spine { + + class SP_API IkConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit IkConstraintTimeline(size_t frameCount, size_t bezierCount, int ikConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time, mix and bend direction of the specified keyframe. + void setFrame(int frame, float time, float mix, float softness, int bendDirection, bool compress, bool stretch); + + int getIkConstraintIndex() { return _constraintIndex; } + + void setIkConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 6; + static const int MIX = 1; + static const int SOFTNESS = 2; + static const int BEND_DIRECTION = 3; + static const int COMPRESS = 4; + static const int STRETCH = 5; + }; +} + +#endif /* Spine_IkConstraintTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Inherit.h b/Projects/SpineCpp/spine-cpp/include/spine/Inherit.h new file mode 100644 index 000000000..aa2253f77 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Inherit.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_TransformMode_h +#define Spine_TransformMode_h + +namespace spine { + enum Inherit { + Inherit_Normal = 0, + Inherit_OnlyTranslation, + Inherit_NoRotationOrReflection, + Inherit_NoScale, + Inherit_NoScaleOrReflection + }; +} + +#endif /* Spine_TransformMode_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/InheritTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/InheritTimeline.h new file mode 100644 index 000000000..67e6bc8e8 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/InheritTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_InheritTimeline_h +#define Spine_InheritTimeline_h + +#include + +#include +#include +#include + +namespace spine { + + class SP_API InheritTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit InheritTimeline(size_t frameCount, int boneIndex); + + virtual ~InheritTimeline(); + + void setFrame(int frame, float time, Inherit inherit); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + + static const int ENTRIES = 2; + static const int INHERIT = 1; + }; +} + +#endif /* Spine_InheritTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Json.h b/Projects/SpineCpp/spine-cpp/include/spine/Json.h new file mode 100644 index 000000000..8c275e46b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Json.h @@ -0,0 +1,116 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Json_h +#define Spine_Json_h + +#include + +#ifndef SPINE_JSON_HAVE_PREV +/* spine doesn't use the "prev" link in the Json sibling lists. */ +#define SPINE_JSON_HAVE_PREV 0 +#endif + +namespace spine { + class SP_API Json : public SpineObject { + friend class SkeletonJson; + + public: + /* Json Types: */ + static const int JSON_FALSE; + static const int JSON_TRUE; + static const int JSON_NULL; + static const int JSON_NUMBER; + static const int JSON_STRING; + static const int JSON_ARRAY; + static const int JSON_OBJECT; + + /* Get item "string" from object. Case insensitive. */ + static Json *getItem(Json *object, const char *string); + + static Json *getItem(Json *object, int childIndex); + + static const char *getString(Json *object, const char *name, const char *defaultValue); + + static float getFloat(Json *object, const char *name, float defaultValue); + + static int getInt(Json *object, const char *name, int defaultValue); + + static bool getBoolean(Json *object, const char *name, bool defaultValue); + + /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when Json_create() returns 0. 0 when Json_create() succeeds. */ + static const char *getError(); + + /* Supply a block of JSON, and this returns a Json object you can interrogate. Call Json_dispose when finished. */ + explicit Json(const char *value); + + ~Json(); + + + private: + static const char *_error; + + Json *_next; +#if SPINE_JSON_HAVE_PREV + Json* _prev; /* next/prev allow you to walk array/object chains. Alternatively, use getSize/getItem */ +#endif + Json *_child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int _type; /* The type of the item, as above. */ + int _size; /* The number of children. */ + + const char *_valueString; /* The item's string, if type==JSON_STRING */ + int _valueInt; /* The item's number, if type==JSON_NUMBER */ + float _valueFloat; /* The item's number, if type==JSON_NUMBER */ + + const char *_name; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + + /* Utility to jump whitespace and cr/lf */ + static const char *skip(const char *inValue); + + /* Parser core - when encountering text, process appropriately. */ + static const char *parseValue(Json *item, const char *value); + + /* Parse the input text into an unescaped cstring, and populate item. */ + static const char *parseString(Json *item, const char *str); + + /* Parse the input text to generate a number, and populate the result into item. */ + static const char *parseNumber(Json *item, const char *num); + + /* Build an array from input text. */ + static const char *parseArray(Json *item, const char *value); + + /* Build an object from the text. */ + static const char *parseObject(Json *item, const char *value); + + static int json_strcasecmp(const char *s1, const char *s2); + }; +} + +#endif /* Spine_Json_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/LinkedMesh.h b/Projects/SpineCpp/spine-cpp/include/spine/LinkedMesh.h new file mode 100644 index 000000000..59bd460ca --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/LinkedMesh.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_LinkedMesh_h +#define Spine_LinkedMesh_h + +#include +#include + +namespace spine { + class MeshAttachment; + + class SP_API LinkedMesh : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + public: + LinkedMesh(MeshAttachment *mesh, const int skinIndex, size_t slotIndex, const String &parent, + bool inheritTimeline); + + LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, + bool inheritTimeline); + + private: + MeshAttachment *_mesh; + int _skinIndex; + String _skin; + size_t _slotIndex; + String _parent; + bool _inheritTimeline; + }; +} + +#endif /* Spine_LinkedMesh_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Log.h b/Projects/SpineCpp/spine-cpp/include/spine/Log.h new file mode 100644 index 000000000..3d4fc6549 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Log.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef SPINE_DEBUG_LOG_H +#define SPINE_DEBUG_LOG_H + +#include + +namespace spine { + SP_API void spDebug_printSkeletonData(SkeletonData *skeletonData); + + SP_API void spDebug_printAnimation(Animation *animation); + + SP_API void spDebug_printTimeline(Timeline *timeline); + + SP_API void spDebug_printBoneDatas(Vector &boneDatas); + + SP_API void spDebug_printBoneData(BoneData *boneData); + + SP_API void spDebug_printSkeleton(Skeleton *skeleton); + + SP_API void spDebug_printBones(Vector &bones); + + SP_API void spDebug_printBone(Bone *bone); + + SP_API void spDebug_printFloats(float *values, int numFloats); + + SP_API void spDebug_printFloats(Vector &values); +} + +#endif diff --git a/Projects/SpineCpp/spine-cpp/include/spine/MathUtil.h b/Projects/SpineCpp/spine-cpp/include/spine/MathUtil.h new file mode 100644 index 000000000..7a2f37976 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/MathUtil.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_MathUtil_h +#define Spine_MathUtil_h + +#include + +#include +// Needed for older MSVC versions +#undef min +#undef max + +namespace spine { + + class SP_API MathUtil : public SpineObject { + private: + MathUtil(); + + public: + static const float Pi; + static const float Pi_2; + static const float InvPi_2; + static const float Deg_Rad; + static const float Rad_Deg; + + template + static inline T min(T a, T b) { return a < b ? a : b; } + + template + static inline T max(T a, T b) { return a > b ? a : b; } + + static float sign(float val); + + static float clamp(float x, float lower, float upper); + + static float abs(float v); + + /// Returns the sine in radians from a lookup table. + static float sin(float radians); + + /// Returns the cosine in radians from a lookup table. + static float cos(float radians); + + /// Returns the sine in radians from a lookup table. + static float sinDeg(float degrees); + + /// Returns the cosine in radians from a lookup table. + static float cosDeg(float degrees); + + /// Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323 + /// degrees), largest error of 0.00488 radians (0.2796 degrees). + static float atan2(float y, float x); + + static float atan2Deg(float x, float y); + + static float acos(float v); + + static float sqrt(float v); + + static float fmod(float a, float b); + + static bool isNan(float v); + + static float quietNan(); + + static float random(); + + static float randomTriangular(float min, float max); + + static float randomTriangular(float min, float max, float mode); + + static float pow(float a, float b); + + static float ceil(float v); + }; + + struct SP_API Interpolation { + virtual float apply(float a) = 0; + + virtual float interpolate(float start, float end, float a) { + return start + (end - start) * apply(a); + } + + virtual ~Interpolation() {}; + }; + + struct SP_API PowInterpolation : public Interpolation { + PowInterpolation(int power) : power(power) { + } + + float apply(float a) { + if (a <= 0.5f) return MathUtil::pow(a * 2.0f, (float) power) / 2.0f; + return MathUtil::pow((a - 1.0f) * 2.0f, (float) power) / (power % 2 == 0 ? -2.0f : 2.0f) + 1.0f; + } + + int power; + }; + + struct SP_API PowOutInterpolation : public Interpolation { + PowOutInterpolation(int power) : power(power) { + } + + float apply(float a) { + return MathUtil::pow(a - 1, (float) power) * (power % 2 == 0 ? -1.0f : 1.0f) + 1.0f; + } + + int power; + }; + +} + +#endif /* Spine_MathUtil_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/MeshAttachment.h b/Projects/SpineCpp/spine-cpp/include/spine/MeshAttachment.h new file mode 100644 index 000000000..df8ed3a3e --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/MeshAttachment.h @@ -0,0 +1,122 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_MeshAttachment_h +#define Spine_MeshAttachment_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + /// Attachment that displays a texture region using a mesh. + class SP_API MeshAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AtlasAttachmentLoader; + + RTTI_DECL + + public: + explicit MeshAttachment(const String &name); + + virtual ~MeshAttachment(); + + using VertexAttachment::computeWorldVertices; + + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride = 2); + + void updateRegion(); + + int getHullLength(); + + void setHullLength(int inValue); + + Vector &getRegionUVs(); + + /// The UV pair for each vertex, normalized within the entire texture. See also MeshAttachment::updateRegion + Vector &getUVs(); + + Vector &getTriangles(); + + Color &getColor(); + + const String &getPath(); + + void setPath(const String &inValue); + + TextureRegion *getRegion(); + + void setRegion(TextureRegion *region); + + Sequence *getSequence(); + + void setSequence(Sequence *sequence); + + MeshAttachment *getParentMesh(); + + void setParentMesh(MeshAttachment *inValue); + + // Nonessential. + Vector &getEdges(); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + virtual Attachment *copy(); + + MeshAttachment *newLinkedMesh(); + + private: + MeshAttachment *_parentMesh; + Vector _uvs; + Vector _regionUVs; + Vector _triangles; + Vector _edges; + String _path; + Color _color; + int _hullLength; + int _width, _height; + TextureRegion *_region; + Sequence *_sequence; + }; +} + +#endif /* Spine_MeshAttachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/MixBlend.h b/Projects/SpineCpp/spine-cpp/include/spine/MixBlend.h new file mode 100644 index 000000000..4d0bd52a7 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/MixBlend.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_MixPose_h +#define Spine_MixPose_h + +namespace spine { + +/// Controls how a timeline is mixed with the setup or current pose. +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, Blend, MixDirection) + enum MixBlend { + MixBlend_Setup = 0, + MixBlend_First, + MixBlend_Replace, + MixBlend_Add + }; +} + +#endif /* Spine_MixPose_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/MixDirection.h b/Projects/SpineCpp/spine-cpp/include/spine/MixDirection.h new file mode 100644 index 000000000..80b3e9999 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/MixDirection.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_MixDirection_h +#define Spine_MixDirection_h + +namespace spine { + +/// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose) or mixing in toward 1 (the timeline's pose). +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, MixPose, MixDirection) + enum MixDirection { + MixDirection_In = 0, + MixDirection_Out + }; + +} + +#endif /* Spine_MixDirection_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PathAttachment.h b/Projects/SpineCpp/spine-cpp/include/spine/PathAttachment.h new file mode 100644 index 000000000..9e92624ed --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PathAttachment.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PathAttachment_h +#define Spine_PathAttachment_h + +#include +#include + +namespace spine { + class SP_API PathAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathAttachment(const String &name); + + /// The length in the setup pose from the start of the path to the end of each curve. + Vector &getLengths(); + + bool isClosed(); + + void setClosed(bool inValue); + + bool isConstantSpeed(); + + void setConstantSpeed(bool inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + Vector _lengths; + bool _closed; + bool _constantSpeed; + Color _color; + }; +} + +#endif /* Spine_PathAttachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PathConstraint.h b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraint.h new file mode 100644 index 000000000..769b0216d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraint.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PathConstraint_h +#define Spine_PathConstraint_h + +#include + +#include + +namespace spine { + class PathConstraintData; + + class Skeleton; + + class PathAttachment; + + class Bone; + + class Slot; + + class SP_API PathConstraint : public Updatable { + friend class Skeleton; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + RTTI_DECL + + public: + PathConstraint(PathConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + PathConstraintData &getData(); + + Vector &getBones(); + + Slot *getTarget(); + + void setTarget(Slot *inValue); + + float getPosition(); + + void setPosition(float inValue); + + float getSpacing(); + + void setSpacing(float inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + static const float EPSILON; + static const int NONE; + static const int BEFORE; + static const int AFTER; + + PathConstraintData &_data; + Vector _bones; + Slot *_target; + float _position, _spacing; + float _mixRotate, _mixX, _mixY; + + Vector _spaces; + Vector _positions; + Vector _world; + Vector _curves; + Vector _lengths; + Vector _segments; + + bool _active; + + Vector &computeWorldPositions(PathAttachment &path, int spacesCount, bool tangents); + + static void addBeforePosition(float p, Vector &temp, int i, Vector &output, int o); + + static void addAfterPosition(float p, Vector &temp, int i, Vector &output, int o); + + static void + addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + Vector &output, int o, bool tangents); + }; +} + +#endif /* Spine_PathConstraint_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintData.h b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintData.h new file mode 100644 index 000000000..0354470bb --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintData.h @@ -0,0 +1,119 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PathConstraintData_h +#define Spine_PathConstraintData_h + +#include +#include +#include +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SlotData; + + class SP_API PathConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class PathConstraint; + + friend class Skeleton; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + public: + RTTI_DECL + + explicit PathConstraintData(const String &name); + + Vector &getBones(); + + SlotData *getTarget(); + + void setTarget(SlotData *inValue); + + PositionMode getPositionMode(); + + void setPositionMode(PositionMode inValue); + + SpacingMode getSpacingMode(); + + void setSpacingMode(SpacingMode inValue); + + RotateMode getRotateMode(); + + void setRotateMode(RotateMode inValue); + + float getOffsetRotation(); + + void setOffsetRotation(float inValue); + + float getPosition(); + + void setPosition(float inValue); + + float getSpacing(); + + void setSpacing(float inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + private: + Vector _bones; + SlotData *_target; + PositionMode _positionMode; + SpacingMode _spacingMode; + RotateMode _rotateMode; + float _offsetRotation; + float _position, _spacing; + float _mixRotate, _mixX, _mixY; + }; +} + +#endif /* Spine_PathConstraintData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintMixTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintMixTimeline.h new file mode 100644 index 000000000..5a6b7bba3 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintMixTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_PathConstraintMixTimeline_h +#define Spine_PathConstraintMixTimeline_h + +#include + +namespace spine { + + class SP_API PathConstraintMixTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathConstraintMixTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and mixes of the specified keyframe. + void setFrame(int frameIndex, float time, float mixRotate, float mixX, float mixY); + + int getPathConstraintIndex() { return _constraintIndex; } + + void setPathConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 4; + static const int ROTATE = 1; + static const int X = 2; + static const int Y = 2; + }; +} + +#endif /* Spine_PathConstraintMixTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintPositionTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintPositionTimeline.h new file mode 100644 index 000000000..4d9301ece --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintPositionTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_PathConstraintPositionTimeline_h +#define Spine_PathConstraintPositionTimeline_h + +#include + +namespace spine { + + class SP_API PathConstraintPositionTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + static const int ENTRIES; + + explicit PathConstraintPositionTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual ~PathConstraintPositionTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPathConstraintIndex() { return _constraintIndex; } + + void setPathConstraintIndex(int inValue) { _constraintIndex = inValue; } + + protected: + int _constraintIndex; + }; +} + +#endif /* Spine_PathConstraintPositionTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintSpacingTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintSpacingTimeline.h new file mode 100644 index 000000000..506f0eb3d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PathConstraintSpacingTimeline.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PathConstraintSpacingTimeline_h +#define Spine_PathConstraintSpacingTimeline_h + +#include + +namespace spine { + class SP_API PathConstraintSpacingTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathConstraintSpacingTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPathConstraintIndex() { return _pathConstraintIndex; } + + void setPathConstraintIndex(int inValue) { _pathConstraintIndex = inValue; } + + protected: + int _pathConstraintIndex; + }; +} + +#endif /* Spine_PathConstraintSpacingTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Physics.h b/Projects/SpineCpp/spine-cpp/include/spine/Physics.h new file mode 100644 index 000000000..d56eab502 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Physics.h @@ -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. + *****************************************************************************/ +#ifndef Spine_Physics_h +#define Spine_Physics_h + +/** Determines how physics and other non-deterministic updates are applied. */ +namespace spine { + enum Physics { + /** Physics are not updated or applied. */ + Physics_None, + + /** Physics are reset to the current pose. */ + Physics_Reset, + + /** Physics are updated and the pose from physics is applied. */ + Physics_Update, + + /** Physics are not updated but the pose from physics is applied. */ + Physics_Pose + }; +} + +#endif \ No newline at end of file diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraint.h b/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraint.h new file mode 100644 index 000000000..39dc603dd --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraint.h @@ -0,0 +1,197 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraint_h +#define Spine_PhysicsConstraint_h + +#include + +#include + +namespace spine { + class PhysicsConstraintData; + + class Skeleton; + + class Bone; + + class SP_API PhysicsConstraint : public Updatable { + + friend class Skeleton; + + friend class PhysicsConstraintTimeline; + + friend class PhysicsConstraintInertiaTimeline; + + friend class PhysicsConstraintStrengthTimeline; + + friend class PhysicsConstraintDampingTimeline; + + friend class PhysicsConstraintMassTimeline; + + friend class PhysicsConstraintWindTimeline; + + friend class PhysicsConstraintGravityTimeline; + + friend class PhysicsConstraintMixTimeline; + + friend class PhysicsConstraintResetTimeline; + + RTTI_DECL + + public: + PhysicsConstraint(PhysicsConstraintData& data, Skeleton& skeleton); + + PhysicsConstraintData &getData(); + + void setBone(Bone* bone); + Bone* getBone(); + + void setInertia(float value); + float getInertia(); + + void setStrength(float value); + float getStrength(); + + void setDamping(float value); + float getDamping(); + + void setMassInverse(float value); + float getMassInverse(); + + void setWind(float value); + float getWind(); + + void setGravity(float value); + float getGravity(); + + void setMix(float value); + float getMix(); + + void setReset(bool value); + bool getReset(); + + void setUx(float value); + float getUx(); + + void setUy(float value); + float getUy(); + + void setCx(float value); + float getCx(); + + void setCy(float value); + float getCy(); + + void setTx(float value); + float getTx(); + + void setTy(float value); + float getTy(); + + void setXOffset(float value); + float getXOffset(); + + void setXVelocity(float value); + float getXVelocity(); + + void setYOffset(float value); + float getYOffset(); + + void setYVelocity(float value); + float getYVelocity(); + + void setRotateOffset(float value); + float getRotateOffset(); + + void setRotateVelocity(float value); + float getRotateVelocity(); + + void setScaleOffset(float value); + float getScaleOffset(); + + void setScaleVelocity(float value); + float getScaleVelocity(); + + void setActive(bool value); + bool isActive(); + + void setRemaining(float value); + float getRemaining(); + + void setLastTime(float value); + float getLastTime(); + + void reset(); + + void setToSetupPose(); + + virtual void update(Physics physics); + + void translate(float x, float y); + + void rotate(float x, float y, float degrees); + + private: + PhysicsConstraintData& _data; + Bone* _bone; + + float _inertia; + float _strength; + float _damping; + float _massInverse; + float _wind; + float _gravity; + float _mix; + + bool _reset; + float _ux; + float _uy; + float _cx; + float _cy; + float _tx; + float _ty; + float _xOffset; + float _xVelocity; + float _yOffset; + float _yVelocity; + float _rotateOffset; + float _rotateVelocity; + float _scaleOffset; + float _scaleVelocity; + + bool _active; + + Skeleton& _skeleton; + float _remaining; + float _lastTime; + }; +} + +#endif /* Spine_PhysicsConstraint_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintData.h b/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintData.h new file mode 100644 index 000000000..c3bb9c338 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintData.h @@ -0,0 +1,151 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraintData_h +#define Spine_PhysicsConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API PhysicsConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Skeleton; + + friend class PhysicsConstraint; + + public: + RTTI_DECL + + explicit PhysicsConstraintData(const String &name); + + void setBone(BoneData* bone); + + BoneData* getBone() const; + + void setX(float x); + + float getX() const; + + void setY(float y); + + float getY() const; + + void setRotate(float rotate); + + float getRotate() const; + + void setScaleX(float scaleX); + + float getScaleX() const; + + void setShearX(float shearX); + + float getShearX() const; + + void setLimit(float limit); + + float getLimit() const; + + void setStep(float step); + + float getStep() const; + + void setInertia(float inertia); + + float getInertia() const; + + void setStrength(float strength); + + float getStrength() const; + + void setDamping(float damping); + + float getDamping() const; + + void setMassInverse(float massInverse); + + float getMassInverse() const; + + void setWind(float wind); + + float getWind() const; + + void setGravity(float gravity); + + float getGravity() const; + + void setMix(float mix); + + float getMix() const; + + void setInertiaGlobal(bool inertiaGlobal); + + bool isInertiaGlobal() const; + + void setStrengthGlobal(bool strengthGlobal); + + bool isStrengthGlobal() const; + + void setDampingGlobal(bool dampingGlobal); + + bool isDampingGlobal() const; + + void setMassGlobal(bool massGlobal); + + bool isMassGlobal() const; + + void setWindGlobal(bool windGlobal); + + bool isWindGlobal() const; + + void setGravityGlobal(bool gravityGlobal); + + bool isGravityGlobal() const; + + void setMixGlobal(bool mixGlobal); + + bool isMixGlobal() const; + + private: + BoneData *_bone; + float _x, _y, _rotate, _scaleX, _shearX, _limit; + float _step, _inertia, _strength, _damping, _massInverse, _wind, _gravity, _mix; + bool _inertiaGlobal, _strengthGlobal, _dampingGlobal, _massGlobal, _windGlobal, _gravityGlobal, _mixGlobal; + }; +} + +#endif /* Spine_PhysicsConstraintData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintTimeline.h new file mode 100644 index 000000000..99ebd1960 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PhysicsConstraintTimeline.h @@ -0,0 +1,288 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraintTimeline_h +#define Spine_PhysicsConstraintTimeline_h + +#include +#include +#include + +namespace spine { + + class SP_API PhysicsConstraintTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex, Property property); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPhysicsConstraintIndex() { return _constraintIndex; } + + void setPhysicsConstraintIndex(int inValue) { _constraintIndex = inValue; } + + protected: + virtual float setup(PhysicsConstraint *constraint) = 0; + virtual float get(PhysicsConstraint *constraint) = 0; + virtual void set(PhysicsConstraint *constraint, float value) = 0; + virtual bool global(PhysicsConstraintData &constraintData) = 0; + + private: + int _constraintIndex; + }; + + class SP_API PhysicsConstraintInertiaTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintInertiaTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintInertia) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getInertia(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_inertia; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_inertia = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isInertiaGlobal(); + } + }; + + class SP_API PhysicsConstraintStrengthTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintStrengthTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintStrength) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getStrength(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_strength; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_strength = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isStrengthGlobal(); + } + }; + + class SP_API PhysicsConstraintDampingTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintDampingTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintDamping) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getDamping(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_damping; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_damping = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isDampingGlobal(); + } + }; + + class SP_API PhysicsConstraintMassTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintMassTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintMass) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return 1 / constraint->_data.getMassInverse(); + } + + float get(PhysicsConstraint *constraint) { + return 1 / constraint->_massInverse; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_massInverse = 1 / value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isMassGlobal(); + } + }; + + class SP_API PhysicsConstraintWindTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintWindTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintWind) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getWind(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_wind; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_wind = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isWindGlobal(); + } + }; + + class SP_API PhysicsConstraintGravityTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintGravityTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintGravity) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getGravity(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_gravity; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_gravity = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isGravityGlobal(); + } + }; + + class SP_API PhysicsConstraintMixTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintMixTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintMix) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getMix(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_mix; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_mix = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isMixGlobal(); + } + }; + + class SP_API PhysicsConstraintResetTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintResetTimeline(size_t frameCount, int physicsConstraintIndex): Timeline(frameCount, 1), _constraintIndex(physicsConstraintIndex) { + PropertyId ids[] = {((PropertyId)Property_PhysicsConstraintReset) << 32}; + setPropertyIds(ids, 1); + } + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(int frame, float time) { + _frames[frame] = time; + } + private: + int _constraintIndex; + }; +} + +#endif /* Spine_PhysicsConstraintTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PointAttachment.h b/Projects/SpineCpp/spine-cpp/include/spine/PointAttachment.h new file mode 100644 index 000000000..672f34242 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PointAttachment.h @@ -0,0 +1,81 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PointAttachment_h +#define Spine_PointAttachment_h + +#include +#include + +namespace spine { + class Bone; + + /// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be + /// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a + /// skin. + /// + /// See http://esotericsoftware.com/spine-point-attachments for Point Attachments in the Spine User Guide. + /// + class SP_API PointAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PointAttachment(const String &name); + + void computeWorldPosition(Bone &bone, float &ox, float &oy); + + float computeWorldRotation(Bone &bone); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getRotation(); + + void setRotation(float inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + float _x, _y, _rotation; + Color _color; + }; +} + +#endif /* Spine_PointAttachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Pool.h b/Projects/SpineCpp/spine-cpp/include/spine/Pool.h new file mode 100644 index 000000000..ed8e6110b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Pool.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Pool_h +#define Spine_Pool_h + +#include +#include +#include +#include + +namespace spine { + template + class SP_API Pool : public SpineObject { + public: + Pool() { + } + + ~Pool() { + ContainerUtil::cleanUpVectorOfPointers(_objects); + } + + T *obtain() { + if (_objects.size() > 0) { + T **object = &_objects[_objects.size() - 1]; + T *ret = *object; + _objects.removeAt(_objects.size() - 1); + + return ret; + } else { + T *ret = new(__FILE__, __LINE__) T(); + + return ret; + } + } + + void free(T *object) { + if (!_objects.contains(object)) { + _objects.add(object); + } + } + + private: + Vector _objects; + }; +} + +#endif /* Spine_Pool_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/PositionMode.h b/Projects/SpineCpp/spine-cpp/include/spine/PositionMode.h new file mode 100644 index 000000000..3aea759e4 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/PositionMode.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_PositionMode_h +#define Spine_PositionMode_h + +namespace spine { + enum PositionMode { + PositionMode_Fixed = 0, + PositionMode_Percent + }; +} + +#endif /* Spine_PositionMode_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Property.h b/Projects/SpineCpp/spine-cpp/include/spine/Property.h new file mode 100644 index 000000000..e51ab3eba --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Property.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Property_h +#define Spine_Property_h + +namespace spine { + typedef long long PropertyId; + enum Property { + Property_Rotate = 1 << 0, + Property_X = 1 << 1, + Property_Y = 1 << 2, + Property_ScaleX = 1 << 3, + Property_ScaleY = 1 << 4, + Property_ShearX = 1 << 5, + Property_ShearY = 1 << 6, + Property_Inherit = 1 << 7, + Property_Rgb = 1 << 8, + Property_Alpha = 1 << 9, + Property_Rgb2 = 1 << 10, + Property_Attachment = 1 << 11, + Property_Deform = 1 << 12, + Property_Event = 1 << 13, + Property_DrawOrder = 1 << 14, + Property_IkConstraint = 1 << 15, + Property_TransformConstraint = 1 << 16, + Property_PathConstraintPosition = 1 << 17, + Property_PathConstraintSpacing = 1 << 18, + Property_PathConstraintMix = 1 << 19, + Property_PhysicsConstraintInertia = 1 << 20, + Property_PhysicsConstraintStrength = 1 << 21, + Property_PhysicsConstraintDamping = 1 << 22, + Property_PhysicsConstraintMass = 1 << 23, + Property_PhysicsConstraintWind = 1 << 24, + Property_PhysicsConstraintGravity = 1 << 25, + Property_PhysicsConstraintMix = 1 << 26, + Property_PhysicsConstraintReset = 1 << 27, + Property_Sequence = 1 << 28 + }; +} + +#endif /* Spine_Property_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/RTTI.h b/Projects/SpineCpp/spine-cpp/include/spine/RTTI.h new file mode 100644 index 000000000..6b0027b6c --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/RTTI.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_RTTI_h +#define Spine_RTTI_h + +#include + +namespace spine { + class SP_API RTTI { + public: + explicit RTTI(const char *className); + + RTTI(const char *className, const RTTI &baseRTTI); + + const char *getClassName() const; + + bool isExactly(const RTTI &rtti) const; + + bool instanceOf(const RTTI &rtti) const; + + private: + // Prevent copying + RTTI(const RTTI &obj); + + RTTI &operator=(const RTTI &obj); + + const char *_className; + const RTTI *_pBaseRTTI; + }; +} + +#define RTTI_DECL \ +public: \ +static const spine::RTTI rtti; \ +virtual const spine::RTTI& getRTTI() const; + +#define RTTI_IMPL_NOPARENT(name) \ +const spine::RTTI name::rtti(#name); \ +const spine::RTTI& name::getRTTI() const { return rtti; } + +#define RTTI_IMPL(name, parent) \ +const spine::RTTI name::rtti(#name, parent::rtti); \ +const spine::RTTI& name::getRTTI() const { return rtti; } + +#endif /* Spine_RTTI_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/RegionAttachment.h b/Projects/SpineCpp/spine-cpp/include/spine/RegionAttachment.h new file mode 100644 index 000000000..376ba817c --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/RegionAttachment.h @@ -0,0 +1,140 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_RegionAttachment_h +#define Spine_RegionAttachment_h + +#include +#include +#include +#include +#include + +#include + +#define NUM_UVS 8 + +namespace spine { + class Bone; + + /// Attachment that displays a texture region. + class SP_API RegionAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AtlasAttachmentLoader; + + RTTI_DECL + + public: + explicit RegionAttachment(const String &name); + + virtual ~RegionAttachment(); + + void updateRegion(); + + /// Transforms the attachment's four vertices to world coordinates. + /// @param slot The parent slot. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + 8. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + void computeWorldVertices(Slot &slot, float *worldVertices, size_t offset, size_t stride = 2); + + void computeWorldVertices(Slot &slot, Vector &worldVertices, size_t offset, size_t stride = 2); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getRotation(); + + void setRotation(float inValue); + + float getScaleX(); + + void setScaleX(float inValue); + + float getScaleY(); + + void setScaleY(float inValue); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + Color &getColor(); + + const String &getPath(); + + void setPath(const String &inValue); + + TextureRegion *getRegion(); + + void setRegion(TextureRegion *region); + + Sequence *getSequence(); + + void setSequence(Sequence *sequence); + + Vector &getOffset(); + + Vector &getUVs(); + + virtual Attachment *copy(); + + private: + static const int BLX; + static const int BLY; + static const int ULX; + static const int ULY; + static const int URX; + static const int URY; + static const int BRX; + static const int BRY; + + float _x, _y, _rotation, _scaleX, _scaleY, _width, _height; + Vector _vertexOffset; + Vector _uvs; + String _path; + Color _color; + TextureRegion *_region; + Sequence *_sequence; + }; +} + +#endif /* Spine_RegionAttachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/RotateMode.h b/Projects/SpineCpp/spine-cpp/include/spine/RotateMode.h new file mode 100644 index 000000000..f34ce789f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/RotateMode.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_RotateMode_h +#define Spine_RotateMode_h + +namespace spine { + enum RotateMode { + RotateMode_Tangent = 0, + RotateMode_Chain, + RotateMode_ChainScale + }; +} + +#endif /* Spine_RotateMode_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/RotateTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/RotateTimeline.h new file mode 100644 index 000000000..29ace74fe --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/RotateTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_RotateTimeline_h +#define Spine_RotateTimeline_h + +#include + +namespace spine { + class SP_API RotateTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + RTTI_DECL + + public: + explicit RotateTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_RotateTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/ScaleTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/ScaleTimeline.h new file mode 100644 index 000000000..447824d2a --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/ScaleTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_ScaleTimeline_h +#define Spine_ScaleTimeline_h + +#include + +namespace spine { + class SP_API ScaleTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ScaleXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ScaleYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_ScaleTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Sequence.h b/Projects/SpineCpp/spine-cpp/include/spine/Sequence.h new file mode 100644 index 000000000..bbf631a59 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Sequence.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Sequence_h +#define Spine_Sequence_h + +#include +#include +#include + +namespace spine { + class Slot; + + class Attachment; + + class SkeletonBinary; + class SkeletonJson; + + class SP_API Sequence : public SpineObject { + friend class SkeletonBinary; + friend class SkeletonJson; + public: + Sequence(int count); + + ~Sequence(); + + Sequence *copy(); + + void apply(Slot *slot, Attachment *attachment); + + String getPath(const String &basePath, int index); + + int getId() { return _id; } + + void setId(int id) { _id = id; } + + int getStart() { return _start; } + + void setStart(int start) { _start = start; } + + int getDigits() { return _digits; } + + void setDigits(int digits) { _digits = digits; } + + int getSetupIndex() { return _setupIndex; } + + void setSetupIndex(int setupIndex) { _setupIndex = setupIndex; } + + Vector &getRegions() { return _regions; } + + private: + int _id; + Vector _regions; + int _start; + int _digits; + int _setupIndex; + + int getNextID(); + }; + + enum SequenceMode { + hold = 0, + once = 1, + loop = 2, + pingpong = 3, + onceReverse = 4, + loopReverse = 5, + pingpongReverse = 6 + }; +} + +#endif diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SequenceTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/SequenceTimeline.h new file mode 100644 index 000000000..2f1ab20f7 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SequenceTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_SequenceTimeline_h +#define Spine_SequenceTimeline_h + +#include +#include + +namespace spine { + class Attachment; + + class SP_API SequenceTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit SequenceTimeline(size_t frameCount, int slotIndex, spine::Attachment *attachment); + + virtual ~SequenceTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(int frame, float time, SequenceMode mode, int index, float delay); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + Attachment *getAttachment() { return _attachment; } + + protected: + int _slotIndex; + Attachment *_attachment; + + static const int ENTRIES = 3; + static const int MODE = 1; + static const int DELAY = 2; + }; +} + +#endif /* Spine_SequenceTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/ShearTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/ShearTimeline.h new file mode 100644 index 000000000..8a72133fb --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/ShearTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_ShearTimeline_h +#define Spine_ShearTimeline_h + +#include + +namespace spine { + class SP_API ShearTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ShearXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ShearYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_ShearTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Skeleton.h b/Projects/SpineCpp/spine-cpp/include/spine/Skeleton.h new file mode 100644 index 000000000..cf1df9f88 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Skeleton.h @@ -0,0 +1,285 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Skeleton_h +#define Spine_Skeleton_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + class SkeletonData; + + class Bone; + + class Updatable; + + class Slot; + + class IkConstraint; + + class PathConstraint; + + class PhysicsConstraint; + + class TransformConstraint; + + class Skin; + + class Attachment; + + class SkeletonClipping; + + class SP_API Skeleton : public SpineObject { + friend class AnimationState; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TransformConstraintTimeline; + + friend class RotateTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class TwoColorTimeline; + + public: + explicit Skeleton(SkeletonData *skeletonData); + + ~Skeleton(); + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + void updateCache(); + + void printUpdateCache(); + + /// Updates the world transform for each bone and applies all constraints. + /// + /// See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine + /// Runtimes Guide. + void updateWorldTransform(Physics physics); + + void updateWorldTransform(Physics physics, Bone *parent); + + /// Sets the bones, constraints, and slots to their setup pose values. + void setToSetupPose(); + + /// Sets the bones and constraints to their setup pose values. + void setBonesToSetupPose(); + + void setSlotsToSetupPose(); + + /// @return May be NULL. + Bone *findBone(const String &boneName); + + /// @return May be NULL. + Slot *findSlot(const String &slotName); + + /// Sets a skin by name (see setSkin). + void setSkin(const String &skinName); + + /// Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. + /// If there was no old skin, each slot's setup mode attachment is attached from the new skin. + /// After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling + /// See Skeleton::setSlotsToSetupPose() + /// Also, often AnimationState::apply(Skeleton&) is called before the next time the + /// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin. + /// @param newSkin May be NULL. + void setSkin(Skin *newSkin); + + /// @return May be NULL. + Attachment *getAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + Attachment *getAttachment(int slotIndex, const String &attachmentName); + + /// @param attachmentName May be empty. + void setAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + IkConstraint *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraint *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraint *findPathConstraint(const String &constraintName); + + /// @return May be NULL. + PhysicsConstraint *findPhysicsConstraint(const String &constraintName); + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// @param outX The horizontal distance between the skeleton origin and the left side of the AABB. + /// @param outY The vertical distance between the skeleton origin and the bottom side of the AABB. + /// @param outWidth The width of the AABB + /// @param outHeight The height of the AABB. + /// @param outVertexBuffer Reference to hold a Vector of floats. This method will assign it with new floats as needed. + // @param clipping Pointer to a SkeletonClipping instance or NULL. If a clipper is given, clipping attachments will be taken into account. + void getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer); + void getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer, SkeletonClipping *clipper); + + Bone *getRootBone(); + + SkeletonData *getData(); + + Vector &getBones(); + + Vector &getUpdateCacheList(); + + Vector &getSlots(); + + Vector &getDrawOrder(); + + Vector &getIkConstraints(); + + Vector &getPathConstraints(); + + Vector &getTransformConstraints(); + + Vector &getPhysicsConstraints(); + + Skin *getSkin(); + + Color &getColor(); + + void setPosition(float x, float y); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getScaleX(); + + void setScaleX(float inValue); + + float getScaleY(); + + void setScaleY(float inValue); + + float getTime(); + + void setTime(float time); + + void update(float delta); + + /// Rotates the physics constraint so next {@link #update(Physics)} forces are applied as if the bone rotated around the + /// specified point in world space. + void physicsTranslate(float x, float y); + + /// Calls {@link PhysicsConstraint#rotate(float, float, float)} for each physics constraint. */ + void physicsRotate(float x, float y, float degrees); + + private: + SkeletonData *_data; + Vector _bones; + Vector _slots; + Vector _drawOrder; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + Vector _physicsConstraints; + Vector _updateCache; + Skin *_skin; + Color _color; + float _scaleX, _scaleY; + float _x, _y; + float _time; + + void sortIkConstraint(IkConstraint *constraint); + + void sortPathConstraint(PathConstraint *constraint); + + void sortPhysicsConstraint(PhysicsConstraint *constraint); + + void sortTransformConstraint(TransformConstraint *constraint); + + void sortPathConstraintAttachment(Skin *skin, size_t slotIndex, Bone &slotBone); + + void sortPathConstraintAttachment(Attachment *attachment, Bone &slotBone); + + void sortBone(Bone *bone); + + static void sortReset(Vector &bones); + }; +} + +#endif /* Spine_Skeleton_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SkeletonBinary.h b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonBinary.h new file mode 100644 index 000000000..7415dc1a2 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonBinary.h @@ -0,0 +1,178 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_SkeletonBinary_h +#define Spine_SkeletonBinary_h + +#include +#include +#include +#include +#include + +namespace spine { + class SkeletonData; + + class Atlas; + + class AttachmentLoader; + + class LinkedMesh; + + class Skin; + + class Attachment; + + class VertexAttachment; + + class Animation; + + class Timeline; + + class CurveTimeline; + + class CurveTimeline1; + + class CurveTimeline2; + + class Sequence; + + class SP_API SkeletonBinary : public SpineObject { + public: + static const int BONE_ROTATE = 0; + static const int BONE_TRANSLATE = 1; + static const int BONE_TRANSLATEX = 2; + static const int BONE_TRANSLATEY = 3; + static const int BONE_SCALE = 4; + static const int BONE_SCALEX = 5; + static const int BONE_SCALEY = 6; + static const int BONE_SHEAR = 7; + static const int BONE_SHEARX = 8; + static const int BONE_SHEARY = 9; + static const int BONE_INHERIT = 10; + + static const int SLOT_ATTACHMENT = 0; + static const int SLOT_RGBA = 1; + static const int SLOT_RGB = 2; + static const int SLOT_RGBA2 = 3; + static const int SLOT_RGB2 = 4; + static const int SLOT_ALPHA = 5; + + static const int ATTACHMENT_DEFORM = 0; + static const int ATTACHMENT_SEQUENCE = 1; + + static const int PATH_POSITION = 0; + static const int PATH_SPACING = 1; + static const int PATH_MIX = 2; + + static const int PHYSICS_INERTIA = 0; + static const int PHYSICS_STRENGTH = 1; + static const int PHYSICS_DAMPING = 2; + static const int PHYSICS_MASS = 4; + static const int PHYSICS_WIND = 5; + static const int PHYSICS_GRAVITY = 6; + static const int PHYSICS_MIX = 7; + static const int PHYSICS_RESET = 8; + + static const int CURVE_LINEAR = 0; + static const int CURVE_STEPPED = 1; + static const int CURVE_BEZIER = 2; + + explicit SkeletonBinary(Atlas *atlasArray); + + explicit SkeletonBinary(AttachmentLoader *attachmentLoader, bool ownsLoader = false); + + ~SkeletonBinary(); + + SkeletonData *readSkeletonData(const unsigned char *binary, int length); + + SkeletonData *readSkeletonDataFile(const String &path); + + void setScale(float scale) { _scale = scale; } + + String &getError() { return _error; } + + private: + struct DataInput : public SpineObject { + const unsigned char *cursor; + const unsigned char *end; + }; + + AttachmentLoader *_attachmentLoader; + Vector _linkedMeshes; + String _error; + float _scale; + const bool _ownsLoader; + + void setError(const char *value1, const char *value2); + + char *readString(DataInput *input); + + char *readStringRef(DataInput *input, SkeletonData *skeletonData); + + float readFloat(DataInput *input); + + unsigned char readByte(DataInput *input); + + signed char readSByte(DataInput *input); + + bool readBoolean(DataInput *input); + + int readInt(DataInput *input); + + void readColor(DataInput *input, Color &color); + + int readVarint(DataInput *input, bool optimizePositive); + + Skin *readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential); + + Sequence *readSequence(DataInput *input); + + Attachment *readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName, + SkeletonData *skeletonData, bool nonessential); + + int readVertices(DataInput *input, Vector &vertices, Vector &bones, bool weighted); + + void readFloatArray(DataInput *input, int n, float scale, Vector &array); + + void readShortArray(DataInput *input, Vector &array, int n); + + Animation *readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData); + + void + setBezier(DataInput *input, CurveTimeline *timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale); + + void readTimeline(DataInput *input, Vector &timelines, CurveTimeline1 *timeline, float scale); + + void readTimeline2(DataInput *input, Vector &timelines, CurveTimeline2 *timeline, float scale); + }; +} + +#endif /* Spine_SkeletonBinary_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SkeletonBounds.h b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonBounds.h new file mode 100644 index 000000000..8dc4cab51 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonBounds.h @@ -0,0 +1,121 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_SkeletonBounds_h +#define Spine_SkeletonBounds_h + +#include +#include +#include + +namespace spine { + class Skeleton; + + class BoundingBoxAttachment; + + class Polygon; + + /// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon. + /// The polygon vertices are provided along with convenience methods for doing hit detection. + class SP_API SkeletonBounds : public SpineObject { + public: + SkeletonBounds(); + + ~SkeletonBounds(); + + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// @param skeleton The skeleton. + /// @param updateAabb + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + void update(Skeleton &skeleton, bool updateAabb); + + /// Returns true if the axis aligned bounding box contains the point. + bool aabbcontainsPoint(float x, float y); + + /// Returns true if the axis aligned bounding box intersects the line segment. + bool aabbintersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + bool aabbIntersectsSkeleton(SkeletonBounds bounds); + + /// Returns true if the polygon contains the point. + bool containsPoint(Polygon *polygon, float x, float y); + + /// Returns the first bounding box attachment that contains the point, or NULL. When doing many checks, it is usually more + /// efficient to only call this method if {@link #aabbcontainsPoint(float, float)} returns true. + BoundingBoxAttachment *containsPoint(float x, float y); + + /// Returns the first bounding box attachment that contains the line segment, or NULL. When doing many checks, it is usually + /// more efficient to only call this method if {@link #aabbintersectsSegment(float, float, float, float)} returns true. + BoundingBoxAttachment *intersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the polygon contains the line segment. + bool intersectsSegment(Polygon *polygon, float x1, float y1, float x2, float y2); + + /// Returns the polygon for the given bounding box attachment or null if no + /// polygon can be found for the attachment. Requires a call to update() first. + Polygon *getPolygon(BoundingBoxAttachment *attachment); + + /// Returns the bounding box for the given polygon or null. Requires a call to update() first. + BoundingBoxAttachment * getBoundingBox(Polygon *polygon); + + /// Returns all polygons or an empty vector. Requires a call to update() first. + Vector &getPolygons(); + + /// Returns all bounding boxes. Requires a call to update() first. + Vector &getBoundingBoxes(); + + float getWidth(); + + float getHeight(); + + private: + Pool _polygonPool; + Vector _boundingBoxes; + Vector _polygons; + float _minX, _minY, _maxX, _maxY; + + void aabbCompute(); + }; + + class Polygon : public SpineObject { + public: + Vector _vertices; + int _count; + + Polygon() : _count(0) { + _vertices.ensureCapacity(16); + } + }; +} + +#endif /* Spine_SkeletonBounds_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SkeletonClipping.h b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonClipping.h new file mode 100644 index 000000000..790bb01ee --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonClipping.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_SkeletonClipping_h +#define Spine_SkeletonClipping_h + +#include +#include + +namespace spine { + class Slot; + + class ClippingAttachment; + + class SP_API SkeletonClipping : public SpineObject { + public: + SkeletonClipping(); + + size_t clipStart(Slot &slot, ClippingAttachment *clip); + + void clipEnd(Slot &slot); + + void clipEnd(); + + void + clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength); + + void + clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength, float *uvs, size_t stride); + + void + clipTriangles(Vector &vertices, Vector &triangles, Vector &uvs, size_t stride); + + bool isClipping(); + + Vector &getClippedVertices(); + + Vector &getClippedTriangles(); + + Vector &getClippedUVs(); + + private: + Triangulator _triangulator; + Vector _clippingPolygon; + Vector _clipOutput; + Vector _clippedVertices; + Vector _clippedTriangles; + Vector _clippedUVs; + Vector _scratch; + ClippingAttachment *_clipAttachment; + Vector *> *_clippingPolygons; + + /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */ + bool clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector *clippingArea, + Vector *output); + + static void makeClockwise(Vector &polygon); + }; +} + +#endif /* Spine_SkeletonClipping_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SkeletonData.h b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonData.h new file mode 100644 index 000000000..1fedd7728 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonData.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_SkeletonData_h +#define Spine_SkeletonData_h + +#include +#include + +namespace spine { + class BoneData; + + class SlotData; + + class Skin; + + class EventData; + + class Animation; + + class IkConstraintData; + + class TransformConstraintData; + + class PathConstraintData; + + class PhysicsConstraintData; + +/// Stores the setup pose and all of the stateless data for a skeleton. + class SP_API SkeletonData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Skeleton; + + public: + SkeletonData(); + + ~SkeletonData(); + + /// Finds a bone by comparing each bone's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + BoneData *findBone(const String &boneName); + + /// @return May be NULL. + SlotData *findSlot(const String &slotName); + + /// @return May be NULL. + Skin *findSkin(const String &skinName); + + /// @return May be NULL. + spine::EventData *findEvent(const String &eventDataName); + + /// @return May be NULL. + Animation *findAnimation(const String &animationName); + + /// @return May be NULL. + IkConstraintData *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraintData *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraintData *findPathConstraint(const String &constraintName); + + /// @return May be NULL. + PhysicsConstraintData *findPhysicsConstraint(const String &constraintName); + + const String &getName(); + + void setName(const String &inValue); + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + Vector &getBones(); + + Vector &getSlots(); + + /// All skins, including the default skin. + Vector &getSkins(); + + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// @return May be NULL. + Skin *getDefaultSkin(); + + void setDefaultSkin(Skin *inValue); + + Vector &getEvents(); + + Vector &getAnimations(); + + Vector &getIkConstraints(); + + Vector &getTransformConstraints(); + + Vector &getPathConstraints(); + + Vector &getPhysicsConstraints(); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + float getReferenceScale(); + + void setReferenceScale(float inValue); + + /// The Spine version used to export this data, or NULL. + const String &getVersion(); + + void setVersion(const String &inValue); + + const String &getHash(); + + void setHash(const String &inValue); + + const String &getImagesPath(); + + void setImagesPath(const String &inValue); + + const String &getAudioPath(); + + void setAudioPath(const String &inValue); + + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + float getFps(); + + void setFps(float inValue); + + private: + String _name; + Vector _bones; // Ordered parents first + Vector _slots; // Setup pose draw order. + Vector _skins; + Skin *_defaultSkin; + Vector _events; + Vector _animations; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + Vector _physicsConstraints; + float _x, _y, _width, _height; + float _referenceScale; + String _version; + String _hash; + Vector _strings; + + // Nonessential. + float _fps; + String _imagesPath; + String _audioPath; + }; +} + +#endif /* Spine_SkeletonData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SkeletonJson.h b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonJson.h new file mode 100644 index 000000000..754d7de81 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonJson.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_SkeletonJson_h +#define Spine_SkeletonJson_h + +#include +#include +#include + +namespace spine { + class Timeline; + + class CurveTimeline; + + class CurveTimeline1; + + class CurveTimeline2; + + class VertexAttachment; + + class Animation; + + class Json; + + class SkeletonData; + + class Atlas; + + class AttachmentLoader; + + class LinkedMesh; + + class String; + + class Sequence; + + class SP_API SkeletonJson : public SpineObject { + public: + explicit SkeletonJson(Atlas *atlas); + + explicit SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader = false); + + ~SkeletonJson(); + + SkeletonData *readSkeletonDataFile(const String &path); + + SkeletonData *readSkeletonData(const char *json); + + void setScale(float scale) { _scale = scale; } + + String &getError() { return _error; } + + private: + AttachmentLoader *_attachmentLoader; + Vector _linkedMeshes; + float _scale; + const bool _ownsLoader; + String _error; + + static Sequence *readSequence(Json *sequence); + + static void + setBezier(CurveTimeline *timeline, int frame, int value, int bezier, float time1, float value1, float cx1, + float cy1, + float cx2, float cy2, float time2, float value2); + + static int + readCurve(Json *curve, CurveTimeline *timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale); + + static Timeline *readTimeline(Json *keyMap, CurveTimeline1 *timeline, float defaultValue, float scale); + + static Timeline * + readTimeline(Json *keyMap, CurveTimeline2 *timeline, const char *name1, const char *name2, float defaultValue, + float scale); + + Animation *readAnimation(Json *root, SkeletonData *skeletonData); + + void readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength); + + void setError(Json *root, const String &value1, const String &value2); + + int findSlotIndex(SkeletonData *skeletonData, const String &slotName, Vector timelines); + }; +} + +#endif /* Spine_SkeletonJson_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SkeletonRenderer.h b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonRenderer.h new file mode 100644 index 000000000..8b2992efc --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SkeletonRenderer.h @@ -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. + *****************************************************************************/ +#ifndef Spine_SkeletonRenderer_h +#define Spine_SkeletonRenderer_h + +#include +#include +#include + +namespace spine { + class Skeleton; + + struct SP_API RenderCommand { + float *positions; + float *uvs; + uint32_t *colors; + uint32_t *darkColors; + int32_t numVertices; + uint16_t *indices; + int32_t numIndices; + BlendMode blendMode; + void *texture; + RenderCommand *next; + }; + + class SP_API SkeletonRenderer: public SpineObject { + public: + explicit SkeletonRenderer(); + + ~SkeletonRenderer(); + + RenderCommand *render(Skeleton &skeleton); + private: + BlockAllocator _allocator; + Vector _worldVertices; + Vector _quadIndices; + SkeletonClipping _clipping; + Vector _renderCommands; + }; +} + +#endif \ No newline at end of file diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Skin.h b/Projects/SpineCpp/spine-cpp/include/spine/Skin.h new file mode 100644 index 000000000..e023eae4b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Skin.h @@ -0,0 +1,171 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Skin_h +#define Spine_Skin_h + +#include +#include +#include + +namespace spine { + class Attachment; + + class Skeleton; + + class BoneData; + + class ConstraintData; + +/// Stores attachments by slot index and attachment name. +/// See SkeletonData::getDefaultSkin, Skeleton::getSkin, and +/// http://esotericsoftware.com/spine-runtime-skins in the Spine Runtimes Guide. + class SP_API Skin : public SpineObject { + friend class Skeleton; + + public: + class SP_API AttachmentMap : public SpineObject { + friend class Skin; + + public: + struct SP_API Entry { + size_t _slotIndex; + String _name; + Attachment *_attachment; + + Entry(size_t slotIndex, const String &name, Attachment *attachment) : + _slotIndex(slotIndex), + _name(name), + _attachment(attachment) { + } + }; + + class SP_API Entries { + friend class AttachmentMap; + + public: + bool hasNext() { + while (true) { + if (_slotIndex >= _buckets.size()) return false; + if (_bucketIndex >= _buckets[_slotIndex].size()) { + _bucketIndex = 0; + ++_slotIndex; + continue; + }; + return true; + } + } + + Entry &next() { + Entry &result = _buckets[_slotIndex][_bucketIndex]; + ++_bucketIndex; + return result; + } + + protected: + Entries(Vector > &buckets) : _buckets(buckets), _slotIndex(0), _bucketIndex(0) { + } + + private: + Vector > &_buckets; + size_t _slotIndex; + size_t _bucketIndex; + }; + + void put(size_t slotIndex, const String &attachmentName, Attachment *attachment); + + Attachment *get(size_t slotIndex, const String &attachmentName); + + void remove(size_t slotIndex, const String &attachmentName); + + Entries getEntries(); + + protected: + AttachmentMap(); + + private: + + int findInBucket(Vector &, const String &attachmentName); + + Vector > _buckets; + }; + + explicit Skin(const String &name); + + ~Skin(); + + /// Adds an attachment to the skin for the specified slot index and name. + /// If the name already exists for the slot, the previous value is replaced. + void setAttachment(size_t slotIndex, const String &name, Attachment *attachment); + + /// Returns the attachment for the specified slot index and name, or NULL. + Attachment *getAttachment(size_t slotIndex, const String &name); + + // Removes the attachment from the skin. + void removeAttachment(size_t slotIndex, const String &name); + + /// Finds the skin keys for a given slot. The results are added to the passed array of names. + /// @param slotIndex The target slotIndex. To find the slot index, use SkeletonData::findSlot and SlotData::getIndex. + /// @param names Found skin key names will be added to this array. + void findNamesForSlot(size_t slotIndex, Vector &names); + + /// Finds the attachments for a given slot. The results are added to the passed array of Attachments. + /// @param slotIndex The target slotIndex. To find the slot index, use SkeletonData::findSlot and SlotData::getIndex. + /// @param attachments Found Attachments will be added to this array. + void findAttachmentsForSlot(size_t slotIndex, Vector &attachments); + + const String &getName(); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. + void addSkin(Skin *other); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. Attachments are deep copied. + void copySkin(Skin *other); + + AttachmentMap::Entries getAttachments(); + + Vector &getBones(); + + Vector &getConstraints(); + + Color &getColor() { return _color; } + + private: + const String _name; + AttachmentMap _attachments; + Vector _bones; + Vector _constraints; + Color _color; + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + void attachAll(Skeleton &skeleton, Skin &oldSkin); + }; +} + +#endif /* Spine_Skin_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Slot.h b/Projects/SpineCpp/spine-cpp/include/spine/Slot.h new file mode 100644 index 000000000..d9a073a7b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Slot.h @@ -0,0 +1,137 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Slot_h +#define Spine_Slot_h + +#include +#include +#include + +namespace spine { + class SlotData; + + class Bone; + + class Skeleton; + + class Attachment; + + class SP_API Slot : public SpineObject { + friend class VertexAttachment; + + friend class Skeleton; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + + public: + Slot(SlotData &data, Bone &bone); + + void setToSetupPose(); + + SlotData &getData(); + + Bone &getBone(); + + Skeleton &getSkeleton(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + /// May be NULL. + Attachment *getAttachment(); + + void setAttachment(Attachment *inValue); + + int getAttachmentState(); + + void setAttachmentState(int state); + + Vector &getDeform(); + + int getSequenceIndex(); + + void setSequenceIndex(int index); + + private: + SlotData &_data; + Bone &_bone; + Skeleton &_skeleton; + Color _color; + Color _darkColor; + bool _hasDarkColor; + Attachment *_attachment; + int _attachmentState; + int _sequenceIndex; + Vector _deform; + }; +} + +#endif /* Spine_Slot_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SlotData.h b/Projects/SpineCpp/spine-cpp/include/spine/SlotData.h new file mode 100644 index 000000000..045198b05 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SlotData.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_SlotData_h +#define Spine_SlotData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API SlotData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + + public: + SlotData(int index, const String &name, BoneData &boneData); + + int getIndex(); + + const String &getName(); + + BoneData &getBoneData(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + void setHasDarkColor(bool inValue); + + /// May be empty. + const String &getAttachmentName(); + + void setAttachmentName(const String &inValue); + + BlendMode getBlendMode(); + + void setBlendMode(BlendMode inValue); + + bool isVisible(); + + void setVisible(bool inValue); + + private: + const int _index; + String _name; + BoneData &_boneData; + Color _color; + Color _darkColor; + + bool _hasDarkColor; + String _attachmentName; + BlendMode _blendMode; + bool _visible; + }; +} + +#endif /* Spine_SlotData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SpacingMode.h b/Projects/SpineCpp/spine-cpp/include/spine/SpacingMode.h new file mode 100644 index 000000000..2e7bdb6de --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SpacingMode.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_SpacingMode_h +#define Spine_SpacingMode_h + +namespace spine { + enum SpacingMode { + SpacingMode_Length = 0, + SpacingMode_Fixed, + SpacingMode_Percent, + SpacingMode_Proportional + }; +} + +#endif /* Spine_SpacingMode_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SpineObject.h b/Projects/SpineCpp/spine-cpp/include/spine/SpineObject.h new file mode 100644 index 000000000..c79cf3bf1 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SpineObject.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Object_h +#define Spine_Object_h + +#include +#include + +#include + +namespace spine { + class String; + + class SP_API SpineObject { + public: + void *operator new(size_t sz); + + void *operator new(size_t sz, const char *file, int line); + + void *operator new(size_t sz, void *ptr); + + void operator delete(void *p, const char *file, int line); + + void operator delete(void *p, void *mem); + + void operator delete(void *p); + + virtual ~SpineObject(); + }; +} + +#endif diff --git a/Projects/SpineCpp/spine-cpp/include/spine/SpineString.h b/Projects/SpineCpp/spine-cpp/include/spine/SpineString.h new file mode 100644 index 000000000..a11a77f8c --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/SpineString.h @@ -0,0 +1,246 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef SPINE_STRING_H +#define SPINE_STRING_H + +#include +#include + +#include +#include + +namespace spine { + class SP_API String : public SpineObject { + public: + String() : _length(0), _buffer(NULL), _tempowner(true) { + } + + String(const char *chars, bool own = false, bool tofree = true) { + _tempowner = tofree; + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + if (!own) { + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, chars, _length + 1); + } else { + _buffer = (char *) chars; + } + } + } + + String(const String &other) { + _tempowner = true; + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, other._buffer, other._length + 1); + } + } + + size_t length() const { + return _length; + } + + bool isEmpty() const { + return _length == 0; + } + + const char *buffer() const { + return _buffer; + } + + void own(const String &other) { + if (this == &other) return; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + _length = other._length; + _buffer = other._buffer; + other._length = 0; + other._buffer = NULL; + } + + void own(const char *chars) { + if (_buffer == chars) return; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = (char *) chars; + } + } + + void unown() { + _length = 0; + _buffer = NULL; + } + + String &operator=(const String &other) { + if (this == &other) return *this; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, other._buffer, other._length + 1); + } + return *this; + } + + String &operator=(const char *chars) { + if (_buffer == chars) return *this; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, chars, _length + 1); + } + return *this; + } + + String &append(const char *chars) { + size_t len = strlen(chars); + size_t thisLen = _length; + _length = _length + len; + bool same = chars == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *) (_buffer + thisLen), (void *) (same ? _buffer : chars), len + 1); + return *this; + } + + String &append(const String &other) { + size_t len = other.length(); + size_t thisLen = _length; + _length = _length + len; + bool same = other._buffer == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *) (_buffer + thisLen), (void *) (same ? _buffer : other._buffer), len + 1); + return *this; + } + + String &append(int other) { + char str[100]; + snprintf(str, 100, "%i", other); + append(str); + return *this; + } + + String &append(float other) { + char str[100]; + snprintf(str, 100, "%f", other); + append(str); + return *this; + } + + bool startsWith(const String &needle) { + if (needle.length() > length()) return false; + for (int i = 0; i < (int)needle.length(); i++) { + if (buffer()[i] != needle.buffer()[i]) return false; + } + return true; + } + + int lastIndexOf(const char c) { + for (int i = (int)length() - 1; i >= 0; i--) { + if (buffer()[i] == c) return i; + } + return -1; + } + + String substring(int startIndex, int length) const { + if (startIndex < 0 || startIndex >= (int)_length || length < 0 || startIndex + length > (int)_length) { + return String(); + } + char* subStr = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(subStr, _buffer + startIndex, length); + subStr[length] = '\0'; + return String(subStr, true, true); + } + + String substring(int startIndex) const { + if (startIndex < 0 || startIndex >= (int)_length) { + return String(); + } + int length = (int)_length - startIndex; + char* subStr = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(subStr, _buffer + startIndex, length); + subStr[length] = '\0'; + return String(subStr, true, true); + } + + friend bool operator==(const String &a, const String &b) { + if (a._buffer == b._buffer) return true; + if (a._length != b._length) return false; + if (a._buffer && b._buffer) { + return strcmp(a._buffer, b._buffer) == 0; + } else { + return false; + } + } + + friend bool operator!=(const String &a, const String &b) { + return !(a == b); + } + + ~String() { + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + } + + private: + mutable size_t _length; + mutable char *_buffer; + mutable bool _tempowner; + }; +} + + +#endif //SPINE_STRING_H diff --git a/Projects/SpineCpp/spine-cpp/include/spine/TextureLoader.h b/Projects/SpineCpp/spine-cpp/include/spine/TextureLoader.h new file mode 100644 index 000000000..fc61541a8 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/TextureLoader.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_TextureLoader_h +#define Spine_TextureLoader_h + +#include +#include + +namespace spine { + class AtlasPage; + + class SP_API TextureLoader : public SpineObject { + public: + TextureLoader(); + + virtual ~TextureLoader(); + + virtual void load(AtlasPage &page, const String &path) = 0; + + virtual void unload(void *texture) = 0; + }; +} + +#endif /* Spine_TextureLoader_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/TextureRegion.h b/Projects/SpineCpp/spine-cpp/include/spine/TextureRegion.h new file mode 100644 index 000000000..876619738 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/TextureRegion.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_TextureRegion_h +#define Spine_TextureRegion_h + +#include + +namespace spine { + class SP_API TextureRegion : public SpineObject { + public: + void *rendererObject; + float u, v, u2, v2; + int degrees; + float offsetX, offsetY; + int width, height; + int originalWidth, originalHeight; + + TextureRegion(): rendererObject(NULL), u(0), v(0), u2(0), v2(0), degrees(0), offsetX(0), offsetY(0), width(0), height(0), originalWidth(0), originalHeight(0) {}; + ~TextureRegion() {}; + }; +} + +#endif diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Timeline.h b/Projects/SpineCpp/spine-cpp/include/spine/Timeline.h new file mode 100644 index 000000000..b8be8f036 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Timeline.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Timeline_h +#define Spine_Timeline_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + class Skeleton; + + class Event; + + class SP_API Timeline : public SpineObject { + RTTI_DECL + + public: + Timeline(size_t frameCount, size_t frameEntries); + + virtual ~Timeline(); + + /// Sets the value(s) for the specified time. + /// @param skeleton The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. + /// @param lastTime lastTime The time this timeline was last applied. Timelines such as EventTimeline trigger only at specific times rather than every frame. In that case, the timeline triggers everything between lastTime (exclusive) and time (inclusive). + /// @param time The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. + /// @param pEvents If any events are fired, they are added to this array. Can be NULL to ignore firing events or if the timeline does not fire events. May be NULL. + /// @param alpha alpha 0 applies the current or setup pose value (depending on pose parameter). 1 applies the timeline + /// value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting alpha over + /// time, an animation can be mixed in or out. alpha can also be useful to apply animations on top of each other (layered). + /// @param blend Controls how mixing is applied when alpha is than 1. + /// @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction) = 0; + + size_t getFrameEntries(); + + size_t getFrameCount(); + + Vector &getFrames(); + + float getDuration(); + + virtual Vector &getPropertyIds(); + + protected: + void setPropertyIds(PropertyId propertyIds[], size_t propertyIdsCount); + + Vector _propertyIds; + Vector _frames; + size_t _frameEntries; + }; +} + +#endif /* Spine_Timeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraint.h b/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraint.h new file mode 100644 index 000000000..a610c4b7a --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraint.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_TransformConstraint_h +#define Spine_TransformConstraint_h + +#include + +#include + +namespace spine { + class TransformConstraintData; + + class Skeleton; + + class Bone; + + class SP_API TransformConstraint : public Updatable { + friend class Skeleton; + + friend class TransformConstraintTimeline; + + RTTI_DECL + + public: + TransformConstraint(TransformConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + TransformConstraintData &getData(); + + Vector &getBones(); + + Bone *getTarget(); + + void setTarget(Bone *inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + float getMixScaleX(); + + void setMixScaleX(float inValue); + + float getMixScaleY(); + + void setMixScaleY(float inValue); + + float getMixShearY(); + + void setMixShearY(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + TransformConstraintData &_data; + Vector _bones; + Bone *_target; + float _mixRotate, _mixX, _mixY, _mixScaleX, _mixScaleY, _mixShearY; + bool _active; + + void applyAbsoluteWorld(); + + void applyRelativeWorld(); + + void applyAbsoluteLocal(); + + void applyRelativeLocal(); + }; +} + +#endif /* Spine_TransformConstraint_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintData.h b/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintData.h new file mode 100644 index 000000000..ad7ce640e --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintData.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintData_h +#define Spine_TransformConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API TransformConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class TransformConstraint; + + friend class Skeleton; + + friend class TransformConstraintTimeline; + + public: + RTTI_DECL + + explicit TransformConstraintData(const String &name); + + Vector &getBones(); + + BoneData *getTarget(); + + void setTarget(BoneData *target); + + float getMixRotate(); + + void setMixRotate(float mixRotate); + + float getMixX(); + + void setMixX(float mixX); + + float getMixY(); + + void setMixY(float mixY); + + float getMixScaleX(); + + void setMixScaleX(float mixScaleX); + + float getMixScaleY(); + + void setMixScaleY(float mixScaleY); + + float getMixShearY(); + + void setMixShearY(float mixShearY); + + float getOffsetRotation(); + + void setOffsetRotation(float offsetRotation); + + float getOffsetX(); + + void setOffsetX(float offsetX); + + float getOffsetY(); + + void setOffsetY(float offsetY); + + float getOffsetScaleX(); + + void setOffsetScaleX(float offsetScaleX); + + float getOffsetScaleY(); + + void setOffsetScaleY(float offsetScaleY); + + float getOffsetShearY(); + + void setOffsetShearY(float offsetShearY); + + bool isRelative(); + + void setRelative(bool isRelative); + + bool isLocal(); + + void setLocal(bool isLocal); + + private: + Vector _bones; + BoneData *_target; + float _mixRotate, _mixX, _mixY, _mixScaleX, _mixScaleY, _mixShearY; + float _offsetRotation, _offsetX, _offsetY, _offsetScaleX, _offsetScaleY, _offsetShearY; + bool _relative, _local; + }; +} + +#endif /* Spine_TransformConstraintData_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintTimeline.h new file mode 100644 index 000000000..ecf99fafa --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/TransformConstraintTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintTimeline_h +#define Spine_TransformConstraintTimeline_h + +#include + +namespace spine { + + class SP_API TransformConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TransformConstraintTimeline(size_t frameCount, size_t bezierCount, int transformConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(size_t frameIndex, float time, float mixRotate, float mixX, float mixY, float mixScaleX, + float mixScaleY, float mixShearY); + + int getTransformConstraintIndex() { return _constraintIndex; } + + void setTransformConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 7; + static const int ROTATE = 1; + static const int X = 2; + static const int Y = 3; + static const int SCALEX = 4; + static const int SCALEY = 5; + static const int SHEARY = 6; + }; +} + +#endif /* Spine_TransformConstraintTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/TranslateTimeline.h b/Projects/SpineCpp/spine-cpp/include/spine/TranslateTimeline.h new file mode 100644 index 000000000..efa9cdec2 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/TranslateTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_TranslateTimeline_h +#define Spine_TranslateTimeline_h + +#include + +#include +#include + +namespace spine { + + class SP_API TranslateTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API TranslateXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API TranslateYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_TranslateTimeline_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Triangulator.h b/Projects/SpineCpp/spine-cpp/include/spine/Triangulator.h new file mode 100644 index 000000000..d10efd06e --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Triangulator.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Triangulator_h +#define Spine_Triangulator_h + +#include +#include + +namespace spine { + class SP_API Triangulator : public SpineObject { + public: + ~Triangulator(); + + Vector &triangulate(Vector &vertices); + + Vector* > & + decompose(Vector + &vertices, + Vector &triangles + ); + + private: + Vector* > + _convexPolygons; + Vector* > + _convexPolygonsIndices; + + Vector _indices; + Vector _isConcaveArray; + Vector _triangles; + + Pool > _polygonPool; + Pool > _polygonIndicesPool; + + static bool isConcave(int index, int vertexCount, Vector &vertices, Vector &indices); + + static bool positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); + + static int winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); + }; +} + +#endif /* Spine_Triangulator_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Updatable.h b/Projects/SpineCpp/spine-cpp/include/spine/Updatable.h new file mode 100644 index 000000000..86e8c8577 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Updatable.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Updatable_h +#define Spine_Updatable_h + +#include +#include +#include + +namespace spine { + class SP_API Updatable : public SpineObject { + RTTI_DECL + + public: + Updatable(); + + virtual ~Updatable(); + + virtual void update(Physics physics) = 0; + + virtual bool isActive() = 0; + + virtual void setActive(bool inValue) = 0; + }; +} + +#endif /* Spine_Updatable_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Vector.h b/Projects/SpineCpp/spine-cpp/include/spine/Vector.h new file mode 100644 index 000000000..771b59791 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Vector.h @@ -0,0 +1,222 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Vector_h +#define Spine_Vector_h + +#include +#include +#include +#include + +namespace spine { + template + class SP_API Vector : public SpineObject { + public: + Vector() : _size(0), _capacity(0), _buffer(NULL) { + } + + Vector(const Vector &inVector) : _size(inVector._size), _capacity(inVector._capacity), _buffer(NULL) { + if (_capacity > 0) { + _buffer = allocate(_capacity); + for (size_t i = 0; i < _size; ++i) { + construct(_buffer + i, inVector._buffer[i]); + } + } + } + + ~Vector() { + clear(); + deallocate(_buffer); + } + + inline void clear() { + for (size_t i = 0; i < _size; ++i) { + destroy(_buffer + (_size - 1 - i)); + } + + _size = 0; + } + + inline size_t getCapacity() const { + return _capacity; + } + + inline size_t size() const { + return _size; + } + + inline void setSize(size_t newSize, const T &defaultValue) { + assert(newSize >= 0); + size_t oldSize = _size; + _size = newSize; + if (_capacity < newSize) { + _capacity = (int) (_size * 1.75f); + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + } + if (oldSize < _size) { + for (size_t i = oldSize; i < _size; i++) { + construct(_buffer + i, defaultValue); + } + } + } + + inline void ensureCapacity(size_t newCapacity = 0) { + if (_capacity >= newCapacity) return; + _capacity = newCapacity; + _buffer = SpineExtension::realloc(_buffer, newCapacity, __FILE__, __LINE__); + } + + inline void add(const T &inValue) { + if (_size == _capacity) { + // inValue might reference an element in this buffer + // When we reallocate, the reference becomes invalid. + // We thus need to create a defensive copy before + // reallocating. + T valueCopy = inValue; + _capacity = (int) (_size * 1.75f); + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + construct(_buffer + _size++, valueCopy); + } else { + construct(_buffer + _size++, inValue); + } + } + + inline void addAll(Vector &inValue) { + ensureCapacity(this->size() + inValue.size()); + for (size_t i = 0; i < inValue.size(); i++) { + add(inValue[i]); + } + } + + inline void clearAndAddAll(Vector &inValue) { + this->clear(); + this->addAll(inValue); + } + + inline void removeAt(size_t inIndex) { + assert(inIndex < _size); + + --_size; + + if (inIndex != _size) { + for (size_t i = inIndex; i < _size; ++i) { + T tmp(_buffer[i]); + _buffer[i] = _buffer[i + 1]; + _buffer[i + 1] = tmp; + } + } + + destroy(_buffer + _size); + } + + inline bool contains(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return true; + } + } + + return false; + } + + inline int indexOf(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return (int) i; + } + } + + return -1; + } + + inline T &operator[](size_t inIndex) { + assert(inIndex < _size); + + return _buffer[inIndex]; + } + + inline friend bool operator==(Vector &lhs, Vector &rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (size_t i = 0, n = lhs.size(); i < n; ++i) { + if (lhs[i] != rhs[i]) { + return false; + } + } + + return true; + } + + inline friend bool operator!=(Vector &lhs, Vector &rhs) { + return !(lhs == rhs); + } + + inline T *buffer() { + return _buffer; + } + + private: + size_t _size; + size_t _capacity; + T *_buffer; + + inline T *allocate(size_t n) { + assert(n > 0); + + T *ptr = SpineExtension::calloc(n, __FILE__, __LINE__); + + assert(ptr); + + return ptr; + } + + inline void deallocate(T *buffer) { + if (_buffer) { + SpineExtension::free(buffer, __FILE__, __LINE__); + } + } + + inline void construct(T *buffer, const T &val) { + new(buffer) T(val); + } + + inline void destroy(T *buffer) { + buffer->~T(); + } + + // Vector &operator=(const Vector &inVector) {}; + }; +} + +#endif /* Spine_Vector_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Version.h b/Projects/SpineCpp/spine-cpp/include/spine/Version.h new file mode 100644 index 000000000..f69bca4b7 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Version.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef SPINE_VERSION_H_ +#define SPINE_VERSION_H_ + +#define SPINE_MAJOR_VERSION 4 +#define SPINE_MINOR_VERSION 2 +#define SPINE_VERSION_STRING "4.2" + +#endif diff --git a/Projects/SpineCpp/spine-cpp/include/spine/VertexAttachment.h b/Projects/SpineCpp/spine-cpp/include/spine/VertexAttachment.h new file mode 100644 index 000000000..23853cd02 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/VertexAttachment.h @@ -0,0 +1,101 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_VertexAttachment_h +#define Spine_VertexAttachment_h + +#include + +#include + +namespace spine { + class Slot; + + /// An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + class SP_API VertexAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class DeformTimeline; + + RTTI_DECL + + public: + explicit VertexAttachment(const String &name); + + virtual ~VertexAttachment(); + + void computeWorldVertices(Slot &slot, float *worldVertices); + + void computeWorldVertices(Slot &slot, Vector &worldVertices); + + /// Transforms local vertices to world coordinates. + /// @param start The index of the first Vertices value to transform. Each vertex has 2 values, x and y. + /// @param count The number of world vertex values to output. Must be less than or equal to WorldVerticesLength - start. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + count. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride = 2); + + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, Vector &worldVertices, size_t offset, + size_t stride = 2); + + /// Gets a unique ID for this attachment. + int getId(); + + Vector &getBones(); + + Vector &getVertices(); + + size_t getWorldVerticesLength(); + + void setWorldVerticesLength(size_t inValue); + + Attachment * getTimelineAttachment(); + + void setTimelineAttachment(Attachment *attachment); + + void copyTo(VertexAttachment *other); + + protected: + Vector _bones; + Vector _vertices; + size_t _worldVerticesLength; + Attachment *_timelineAttachment; + + private: + const int _id; + + static int getNextID(); + }; +} + +#endif /* Spine_VertexAttachment_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/Vertices.h b/Projects/SpineCpp/spine-cpp/include/spine/Vertices.h new file mode 100644 index 000000000..f18201111 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/Vertices.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef Spine_Vertices_h +#define Spine_Vertices_h + +#include + +namespace spine { + class SP_API Vertices : public SpineObject { + public: + Vector _bones; + Vector _vertices; + }; +} + +#endif /* Spine_Vertices_h */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/dll.h b/Projects/SpineCpp/spine-cpp/include/spine/dll.h new file mode 100644 index 000000000..09dff9069 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/dll.h @@ -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. + *****************************************************************************/ + +#ifndef SPINE_SHAREDLIB_H +#define SPINE_SHAREDLIB_H + +#ifdef _WIN32 +#define DLLIMPORT __declspec(dllimport) +#define DLLEXPORT __declspec(dllexport) +#else +#ifndef DLLIMPORT +#define DLLIMPORT +#endif +#ifndef DLLEXPORT +#define DLLEXPORT +#endif +#endif + +#ifdef SPINEPLUGIN_API +#define SP_API SPINEPLUGIN_API +#else +#define SP_API +#endif + +#endif /* SPINE_SHAREDLIB_H */ diff --git a/Projects/SpineCpp/spine-cpp/include/spine/spine.h b/Projects/SpineCpp/spine-cpp/include/spine/spine.h new file mode 100644 index 000000000..c99d61c06 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/include/spine/spine.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef SPINE_SPINE_H_ +#define SPINE_SPINE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Animation.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Animation.cpp new file mode 100644 index 000000000..ba2c70436 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Animation.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include + +using namespace spine; + +Animation::Animation(const String &name, Vector &timelines, float duration) : _timelines(timelines), + _timelineIds(), + _duration(duration), + _name(name) { + assert(_name.length() > 0); + for (size_t i = 0; i < timelines.size(); i++) { + Vector propertyIds = timelines[i]->getPropertyIds(); + for (size_t ii = 0; ii < propertyIds.size(); ii++) + _timelineIds.put(propertyIds[ii], true); + } +} + +bool Animation::hasTimeline(Vector &ids) { + for (size_t i = 0; i < ids.size(); i++) { + if (_timelineIds.containsKey(ids[i])) return true; + } + return false; +} + +Animation::~Animation() { + ContainerUtil::cleanUpVectorOfPointers(_timelines); +} + +void Animation::apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + if (loop && _duration != 0) { + time = MathUtil::fmod(time, _duration); + if (lastTime > 0) { + lastTime = MathUtil::fmod(lastTime, _duration); + } + } + + for (size_t i = 0, n = _timelines.size(); i < n; ++i) { + _timelines[i]->apply(skeleton, lastTime, time, pEvents, alpha, blend, direction); + } +} + +const String &Animation::getName() { + return _name; +} + +Vector &Animation::getTimelines() { + return _timelines; +} + +float Animation::getDuration() { + return _duration; +} + +void Animation::setDuration(float inValue) { + _duration = inValue; +} + +int Animation::search(Vector &frames, float target) { + size_t n = (int) frames.size(); + for (size_t i = 1; i < n; i++) { + if (frames[i] > target) return (int) (i - 1); + } + return (int) (n - 1); +} + +int Animation::search(Vector &frames, float target, int step) { + size_t n = frames.size(); + for (size_t i = step; i < n; i += step) + if (frames[i] > target) return (int) (i - step); + return (int) (n - step); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/AnimationState.cpp b/Projects/SpineCpp/spine-cpp/src/spine/AnimationState.cpp new file mode 100644 index 000000000..dd3b57c08 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/AnimationState.cpp @@ -0,0 +1,1098 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +void dummyOnAnimationEventFunc(AnimationState *state, spine::EventType type, TrackEntry *entry, Event *event = NULL) { + SP_UNUSED(state); + SP_UNUSED(type); + SP_UNUSED(entry); + SP_UNUSED(event); +} + +TrackEntry::TrackEntry() : _animation(NULL), _previous(NULL), _next(NULL), _mixingFrom(NULL), _mixingTo(0), + _trackIndex(0), _loop(false), _holdPrevious(false), _reverse(false), + _shortestRotation(false), + _eventThreshold(0), _mixAttachmentThreshold(0), _alphaAttachmentThreshold(0), _mixDrawOrderThreshold(0), _animationStart(0), + _animationEnd(0), _animationLast(0), _nextAnimationLast(0), _delay(0), _trackTime(0), + _trackLast(0), _nextTrackLast(0), _trackEnd(0), _timeScale(1.0f), _alpha(0), _mixTime(0), + _mixDuration(0), _interruptAlpha(0), _totalAlpha(0), _mixBlend(MixBlend_Replace), + _listener(dummyOnAnimationEventFunc), _listenerObject(NULL) { +} + +TrackEntry::~TrackEntry() {} + +int TrackEntry::getTrackIndex() { return _trackIndex; } + +Animation *TrackEntry::getAnimation() { return _animation; } + +TrackEntry *TrackEntry::getPrevious() { return _previous; } + +bool TrackEntry::getLoop() { return _loop; } + +void TrackEntry::setLoop(bool inValue) { _loop = inValue; } + +bool TrackEntry::getHoldPrevious() { return _holdPrevious; } + +void TrackEntry::setHoldPrevious(bool inValue) { _holdPrevious = inValue; } + +bool TrackEntry::getReverse() { return _reverse; } + +void TrackEntry::setReverse(bool inValue) { _reverse = inValue; } + +bool TrackEntry::getShortestRotation() { return _shortestRotation; } + +void TrackEntry::setShortestRotation(bool inValue) { _shortestRotation = inValue; } + +float TrackEntry::getDelay() { return _delay; } + +void TrackEntry::setDelay(float inValue) { _delay = inValue; } + +float TrackEntry::getTrackTime() { return _trackTime; } + +void TrackEntry::setTrackTime(float inValue) { _trackTime = inValue; } + +float TrackEntry::getTrackEnd() { return _trackEnd; } + +void TrackEntry::setTrackEnd(float inValue) { _trackEnd = inValue; } + +float TrackEntry::getAnimationStart() { return _animationStart; } + +void TrackEntry::setAnimationStart(float inValue) { _animationStart = inValue; } + +float TrackEntry::getAnimationEnd() { return _animationEnd; } + +void TrackEntry::setAnimationEnd(float inValue) { _animationEnd = inValue; } + +float TrackEntry::getAnimationLast() { return _animationLast; } + +void TrackEntry::setAnimationLast(float inValue) { + _animationLast = inValue; + _nextAnimationLast = inValue; +} + +float TrackEntry::getAnimationTime() { + if (_loop) { + float duration = _animationEnd - _animationStart; + if (duration == 0) return _animationStart; + return MathUtil::fmod(_trackTime, duration) + _animationStart; + } + + return MathUtil::min(_trackTime + _animationStart, _animationEnd); +} + +float TrackEntry::getTimeScale() { return _timeScale; } + +void TrackEntry::setTimeScale(float inValue) { _timeScale = inValue; } + +float TrackEntry::getAlpha() { return _alpha; } + +void TrackEntry::setAlpha(float inValue) { _alpha = inValue; } + +float TrackEntry::getEventThreshold() { return _eventThreshold; } + +void TrackEntry::setEventThreshold(float inValue) { _eventThreshold = inValue; } + +float TrackEntry::getMixAttachmentThreshold() { return _mixAttachmentThreshold; } + +void TrackEntry::setMixAttachmentThreshold(float inValue) { _mixAttachmentThreshold = inValue; } + +float TrackEntry::getAlphaAttachmentThreshold() { return _alphaAttachmentThreshold; } + +void TrackEntry::setAlphaAttachmentThreshold(float inValue) { _alphaAttachmentThreshold = inValue; } + +float TrackEntry::getMixDrawOrderThreshold() { return _mixDrawOrderThreshold; } + +void TrackEntry::setMixDrawOrderThreshold(float inValue) { _mixDrawOrderThreshold = inValue; } + +TrackEntry *TrackEntry::getNext() { return _next; } + +bool TrackEntry::isComplete() { + return _trackTime >= _animationEnd - _animationStart; +} + +float TrackEntry::getMixTime() { return _mixTime; } + +void TrackEntry::setMixTime(float inValue) { _mixTime = inValue; } + +float TrackEntry::getMixDuration() { return _mixDuration; } + +void TrackEntry::setMixDuration(float inValue) { _mixDuration = inValue; } + +void TrackEntry::setMixDuration(float mixDuration, float delay) { + _mixDuration = mixDuration; + if (_previous && delay <= 0) delay += _previous->getTrackComplete() - mixDuration; + this->_delay = delay; +} + +TrackEntry *TrackEntry::getMixingFrom() { return _mixingFrom; } + +TrackEntry *TrackEntry::getMixingTo() { return _mixingTo; } + +void TrackEntry::setMixBlend(MixBlend blend) { _mixBlend = blend; } + +MixBlend TrackEntry::getMixBlend() { return _mixBlend; } + +void TrackEntry::resetRotationDirections() { + _timelinesRotation.clear(); +} + +void TrackEntry::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void TrackEntry::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void TrackEntry::reset() { + _animation = NULL; + _previous = NULL; + _next = NULL; + _mixingFrom = NULL; + _mixingTo = NULL; + + setRendererObject(NULL); + + _timelineMode.clear(); + _timelineHoldMix.clear(); + _timelinesRotation.clear(); + + _listener = dummyOnAnimationEventFunc; + _listenerObject = NULL; +} + +float TrackEntry::getTrackComplete() { + float duration = _animationEnd - _animationStart; + if (duration != 0) { + if (_loop) return duration * (1 + (int) (_trackTime / duration));// Completion of next loop. + if (_trackTime < duration) return duration; // Before duration. + } + return _trackTime;// Next update. +} + +bool TrackEntry::wasApplied() { + return _nextTrackLast != -1; +} + +EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event) : _type(eventType), + _entry(trackEntry), + _event(event) { +} + +EventQueue *EventQueue::newEventQueue(AnimationState &state) { + return new (__FILE__, __LINE__) EventQueue(state); +} + +EventQueueEntry EventQueue::newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event) { + return EventQueueEntry(eventType, entry, event); +} + +EventQueue::EventQueue(AnimationState &state) : _state(state), + _drainDisabled(false) { +} + +EventQueue::~EventQueue() { +} + +void EventQueue::start(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Start, entry)); + _state._animationsChanged = true; +} + +void EventQueue::interrupt(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Interrupt, entry)); +} + +void EventQueue::end(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_End, entry)); + _state._animationsChanged = true; +} + +void EventQueue::dispose(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Dispose, entry)); +} + +void EventQueue::complete(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Complete, entry)); +} + +void EventQueue::event(TrackEntry *entry, Event *event) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Event, entry, event)); +} + +/// Raises all events in the queue and drains the queue. +void EventQueue::drain() { + if (_drainDisabled) { + return; + } + + _drainDisabled = true; + + AnimationState &state = _state; + + // Don't cache _eventQueueEntries.size() so callbacks can queue their own events (eg, call setAnimation in AnimationState_Complete). + for (size_t i = 0; i < _eventQueueEntries.size(); ++i) { + EventQueueEntry queueEntry = _eventQueueEntries[i]; + TrackEntry *trackEntry = queueEntry._entry; + + switch (queueEntry._type) { + case EventType_Start: + case EventType_Interrupt: + case EventType_Complete: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + break; + case EventType_End: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + /* Fall through. */ + case EventType_Dispose: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, EventType_Dispose, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, EventType_Dispose, trackEntry, NULL); + else + state._listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + + if (!_state.getManualTrackEntryDisposal()) _state.disposeTrackEntry(trackEntry); + break; + case EventType_Event: + if (!trackEntry->_listenerObject) + trackEntry->_listener(&state, queueEntry._type, trackEntry, queueEntry._event); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, queueEntry._event); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event); + break; + } + } + _eventQueueEntries.clear(); + + _drainDisabled = false; +} + +AnimationState::AnimationState(AnimationStateData *data) : _data(data), + _queue(EventQueue::newEventQueue(*this)), + _animationsChanged(false), + _listener(dummyOnAnimationEventFunc), + _listenerObject(NULL), + _unkeyedState(0), + _timeScale(1), + _manualTrackEntryDisposal(false) { +} + +AnimationState::~AnimationState() { + for (size_t i = 0; i < _tracks.size(); i++) { + TrackEntry *entry = _tracks[i]; + if (entry) { + TrackEntry *from = entry->_mixingFrom; + while (from) { + TrackEntry *curr = from; + from = curr->_mixingFrom; + delete curr; + } + TrackEntry *next = entry->_next; + while (next) { + TrackEntry *curr = next; + next = curr->_next; + delete curr; + } + delete entry; + } + } + delete _queue; +} + +void AnimationState::update(float delta) { + delta *= _timeScale; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL) { + continue; + } + + TrackEntry ¤t = *currentP; + + current._animationLast = current._nextAnimationLast; + current._trackLast = current._nextTrackLast; + + float currentDelta = delta * current._timeScale; + + if (current._delay > 0) { + current._delay -= currentDelta; + if (current._delay > 0) { + continue; + } + currentDelta = -current._delay; + current._delay = 0; + } + + TrackEntry *next = current._next; + if (next != NULL) { + // When the next entry's delay is passed, change to the next entry, preserving leftover time. + float nextTime = current._trackLast - next->_delay; + if (nextTime >= 0) { + next->_delay = 0; + next->_trackTime += + current._timeScale == 0 ? 0 : (nextTime / current._timeScale + delta) * next->_timeScale; + current._trackTime += currentDelta; + setCurrent(i, next, true); + while (next->_mixingFrom != NULL) { + next->_mixTime += delta; + next = next->_mixingFrom; + } + continue; + } + } else if (current._trackLast >= current._trackEnd && current._mixingFrom == NULL) { + // clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. + _tracks[i] = NULL; + + _queue->end(currentP); + clearNext(currentP); + continue; + } + + if (current._mixingFrom != NULL && updateMixingFrom(currentP, delta)) { + // End mixing from entries once all have completed. + TrackEntry *from = current._mixingFrom; + current._mixingFrom = NULL; + if (from != NULL) from->_mixingTo = NULL; + while (from != NULL) { + _queue->end(from); + from = from->_mixingFrom; + } + } + + current._trackTime += currentDelta; + } + + _queue->drain(); +} + +bool AnimationState::apply(Skeleton &skeleton) { + if (_animationsChanged) { + animationsChanged(); + } + + bool applied = false; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL || currentP->_delay > 0) { + continue; + } + + TrackEntry ¤t = *currentP; + + applied = true; + MixBlend blend = i == 0 ? MixBlend_First : current._mixBlend; + + // apply mixing from entries first. + float alpha = current._alpha; + if (current._mixingFrom != NULL) { + alpha *= applyMixingFrom(currentP, skeleton, blend); + } else if (current._trackTime >= current._trackEnd && current._next == NULL) { + alpha = 0;// Set to setup pose the last time the entry will be applied. + } + bool attachments = alpha >= current._alphaAttachmentThreshold; + + + // apply current entry. + float animationLast = current._animationLast, animationTime = current.getAnimationTime(); + float applyTime = animationTime; + Vector *applyEvents = &_events; + if (current._reverse) { + applyTime = current._animation->getDuration() - applyTime; + applyEvents = NULL; + } + size_t timelineCount = current._animation->_timelines.size(); + Vector &timelines = current._animation->_timelines; + if ((i == 0 && alpha == 1) || blend == MixBlend_Add) { + if (i == 0) attachments = true; + for (size_t ii = 0; ii < timelineCount; ++ii) { + Timeline *timeline = timelines[ii]; + if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, blend, + attachments); + else + timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection_In); + } + } else { + Vector &timelineMode = current._timelineMode; + + bool shortestRotation = current._shortestRotation; + bool firstFrame = !shortestRotation && current._timelinesRotation.size() != timelines.size() << 1; + if (firstFrame) current._timelinesRotation.setSize(timelines.size() << 1, 0); + Vector &timelinesRotation = current._timelinesRotation; + + for (size_t ii = 0; ii < timelineCount; ++ii) { + Timeline *timeline = timelines[ii]; + assert(timeline); + + MixBlend timelineBlend = timelineMode[ii] == Subsequent ? blend : MixBlend_Setup; + + if (!shortestRotation && timeline->getRTTI().isExactly(RotateTimeline::rtti)) + applyRotateTimeline(static_cast(timeline), skeleton, applyTime, alpha, + timelineBlend, timelinesRotation, ii << 1, firstFrame); + else if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, + blend, attachments); + else + timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, + MixDirection_In); + } + } + + queueEvents(currentP, animationTime); + _events.clear(); + current._nextAnimationLast = animationTime; + current._nextTrackLast = current._trackTime; + } + + int setupState = _unkeyedState + Setup; + Vector &slots = skeleton.getSlots(); + for (int i = 0, n = (int) slots.size(); i < n; i++) { + Slot *slot = slots[i]; + if (slot->getAttachmentState() == setupState) { + const String &attachmentName = slot->getData().getAttachmentName(); + slot->setAttachment(attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot->getData().getIndex(), attachmentName)); + } + } + _unkeyedState += 2; + + _queue->drain(); + return applied; +} + +void AnimationState::clearTracks() { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) + clearTrack(i); + _tracks.clear(); + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +void AnimationState::clearTrack(size_t trackIndex) { + if (trackIndex >= _tracks.size()) return; + + TrackEntry *current = _tracks[trackIndex]; + if (current == NULL) return; + + _queue->end(current); + + clearNext(current); + + TrackEntry *entry = current; + while (true) { + TrackEntry *from = entry->_mixingFrom; + if (from == NULL) break; + + _queue->end(from); + entry->_mixingFrom = NULL; + entry->_mixingTo = NULL; + entry = from; + } + + _tracks[current->_trackIndex] = NULL; + + _queue->drain(); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, const String &animationName, bool loop) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return setAnimation(trackIndex, animation, loop); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, Animation *animation, bool loop) { + assert(animation != NULL); + + bool interrupt = true; + TrackEntry *current = expandToIndex(trackIndex); + if (current != NULL) { + if (current->_nextTrackLast == -1) { + // Don't mix from an entry that was never applied. + _tracks[trackIndex] = current->_mixingFrom; + _queue->interrupt(current); + _queue->end(current); + clearNext(current); + current = current->_mixingFrom; + interrupt = false; + } else { + clearNext(current); + } + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, current); + setCurrent(trackIndex, entry, interrupt); + _queue->drain(); + + return entry; +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return addAnimation(trackIndex, animation, loop, delay); +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay) { + assert(animation != NULL); + + TrackEntry *last = expandToIndex(trackIndex); + if (last != NULL) { + while (last->_next != NULL) + last = last->_next; + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, last); + + if (last == NULL) { + setCurrent(trackIndex, entry, true); + _queue->drain(); + } else { + last->_next = entry; + entry->_previous = last; + if (delay <= 0) delay += last->getTrackComplete() - entry->_mixDuration; + } + + entry->_delay = delay; + return entry; +} + +TrackEntry *AnimationState::setEmptyAnimation(size_t trackIndex, float mixDuration) { + TrackEntry *entry = setAnimation(trackIndex, AnimationState::getEmptyAnimation(), false); + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +TrackEntry *AnimationState::addEmptyAnimation(size_t trackIndex, float mixDuration, float delay) { + TrackEntry *entry = addAnimation(trackIndex, AnimationState::getEmptyAnimation(), false, delay); + if (delay <= 0) entry->_delay += entry->_mixDuration - mixDuration; + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +void AnimationState::setEmptyAnimations(float mixDuration) { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *current = _tracks[i]; + if (current != NULL) { + setEmptyAnimation(i, mixDuration); + } + } + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +TrackEntry *AnimationState::getCurrent(size_t trackIndex) { + return trackIndex >= _tracks.size() ? NULL : _tracks[trackIndex]; +} + +AnimationStateData *AnimationState::getData() { + return _data; +} + +Vector &AnimationState::getTracks() { + return _tracks; +} + +float AnimationState::getTimeScale() { + return _timeScale; +} + +void AnimationState::setTimeScale(float inValue) { + _timeScale = inValue; +} + +void AnimationState::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void AnimationState::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void AnimationState::disableQueue() { + _queue->_drainDisabled = true; +} + +void AnimationState::enableQueue() { + _queue->_drainDisabled = false; +} + +void AnimationState::setManualTrackEntryDisposal(bool inValue) { + _manualTrackEntryDisposal = inValue; +} + +bool AnimationState::getManualTrackEntryDisposal() { + return _manualTrackEntryDisposal; +} + +void AnimationState::disposeTrackEntry(TrackEntry *entry) { + entry->reset(); + _trackEntryPool.free(entry); +} + +Animation *AnimationState::getEmptyAnimation() { + static Vector timelines; + static Animation ret(String(""), timelines, 0); + return &ret; +} + +void AnimationState::applyAttachmentTimeline(AttachmentTimeline *attachmentTimeline, Skeleton &skeleton, float time, + MixBlend blend, bool attachments) { + Slot *slot = skeleton.getSlots()[attachmentTimeline->getSlotIndex()]; + if (!slot->getBone().isActive()) return; + + Vector &frames = attachmentTimeline->getFrames(); + if (time < frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) + setAttachment(skeleton, *slot, slot->getData().getAttachmentName(), attachments); + } else { + setAttachment(skeleton, *slot, attachmentTimeline->getAttachmentNames()[Animation::search(frames, time)], + attachments); + } + + /* If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.*/ + if (slot->getAttachmentState() <= _unkeyedState) slot->setAttachmentState(_unkeyedState + Setup); +} + + +void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, + MixBlend blend, Vector &timelinesRotation, size_t i, bool firstFrame) { + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) { + rotateTimeline->apply(skeleton, 0, time, NULL, 1, blend, MixDirection_In); + return; + } + + Bone *bone = skeleton._bones[rotateTimeline->_boneIndex]; + if (!bone->isActive()) return; + Vector &frames = rotateTimeline->_frames; + float r1, r2; + if (time < frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_rotation = bone->_data._rotation; + default: + return; + case MixBlend_First: + r1 = bone->_rotation; + r2 = bone->_data._rotation; + } + } else { + r1 = blend == MixBlend_Setup ? bone->_data._rotation : bone->_rotation; + r2 = bone->_data._rotation + rotateTimeline->getCurveValue(time); + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float total, diff = r2 - r1; + diff -= MathUtil::ceil(diff / 360 - 0.5) * 360; + if (diff == 0) { + total = timelinesRotation[i]; + } else { + float lastTotal, lastDiff; + if (firstFrame) { + lastTotal = 0; + lastDiff = diff; + } else { + lastTotal = timelinesRotation[i]; + lastDiff = timelinesRotation[i + 1]; + } + float loops = lastTotal - MathUtil::fmod(lastTotal, 360.f); + total = diff + loops; + bool current = diff >= 0, dir = lastTotal >= 0; + if (MathUtil::abs(lastDiff) <= 90 && MathUtil::sign(lastDiff) != MathUtil::sign(diff)) { + if (MathUtil::abs(lastTotal - loops) > 180) { + total += 360.f * MathUtil::sign(lastTotal); + dir = current; + } else if (loops != 0) + total -= 360.f * MathUtil::sign(lastTotal); + else + dir = current; + } + if (dir != current) { + total += 360 * MathUtil::sign(lastTotal); + } + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + bone->_rotation = r1 + total * alpha; +} + +bool AnimationState::updateMixingFrom(TrackEntry *to, float delta) { + TrackEntry *from = to->_mixingFrom; + if (from == NULL) { + return true; + } + + bool finished = updateMixingFrom(from, delta); + + from->_animationLast = from->_nextAnimationLast; + from->_trackLast = from->_nextTrackLast; + + // 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; +} + +float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBlend blend) { + TrackEntry *from = to->_mixingFrom; + if (from->_mixingFrom != NULL) applyMixingFrom(from, skeleton, blend); + + float mix; + if (to->_mixDuration == 0) { + // Single frame mix to undo mixingFrom changes. + mix = 1; + if (blend == MixBlend_First) blend = MixBlend_Setup; + } else { + mix = to->_mixTime / to->_mixDuration; + if (mix > 1) { + mix = 1; + } + if (blend != MixBlend_First) blend = from->_mixBlend; + } + + bool attachments = mix < from->_mixAttachmentThreshold, drawOrder = mix < from->_mixDrawOrderThreshold; + Vector &timelines = from->_animation->_timelines; + size_t timelineCount = timelines.size(); + float alphaHold = from->_alpha * to->_interruptAlpha, alphaMix = alphaHold * (1 - mix); + float animationLast = from->_animationLast, animationTime = from->getAnimationTime(); + float applyTime = animationTime; + Vector *events = NULL; + if (from->_reverse) { + applyTime = from->_animation->_duration - applyTime; + } else { + if (mix < from->_eventThreshold) events = &_events; + } + + if (blend == MixBlend_Add) { + for (size_t i = 0; i < timelineCount; i++) + timelines[i]->apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection_Out); + } else { + Vector &timelineMode = from->_timelineMode; + Vector &timelineHoldMix = from->_timelineHoldMix; + + bool shortestRotation = from->_shortestRotation; + bool firstFrame = !shortestRotation && from->_timelinesRotation.size() != timelines.size() << 1; + if (firstFrame) from->_timelinesRotation.setSize(timelines.size() << 1, 0); + + Vector &timelinesRotation = from->_timelinesRotation; + + from->_totalAlpha = 0; + for (size_t i = 0; i < timelineCount; i++) { + Timeline *timeline = timelines[i]; + MixDirection direction = MixDirection_Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) { + case Subsequent: + if (!drawOrder && (timeline->getRTTI().isExactly(DrawOrderTimeline::rtti))) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case First: + timelineBlend = MixBlend_Setup; + alpha = alphaMix; + break; + case HoldSubsequent: + timelineBlend = blend; + alpha = alphaHold; + break; + case HoldFirst: + timelineBlend = MixBlend_Setup; + alpha = alphaHold; + break; + default: + timelineBlend = MixBlend_Setup; + TrackEntry *holdMix = timelineHoldMix[i]; + alpha = alphaHold * MathUtil::max(0.0f, 1.0f - holdMix->_mixTime / holdMix->_mixDuration); + break; + } + from->_totalAlpha += alpha; + if (!shortestRotation && (timeline->getRTTI().isExactly(RotateTimeline::rtti))) { + applyRotateTimeline((RotateTimeline *) timeline, skeleton, applyTime, alpha, timelineBlend, + timelinesRotation, i << 1, firstFrame); + } else if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) { + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, timelineBlend, + attachments && alpha >= from->_alphaAttachmentThreshold); + } else { + if (drawOrder && timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) && + timelineBlend == MixBlend_Setup) + direction = MixDirection_In; + timeline->apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to->_mixDuration > 0) { + queueEvents(from, animationTime); + } + + _events.clear(); + from->_nextAnimationLast = animationTime; + from->_nextTrackLast = from->_trackTime; + + return mix; +} + +void AnimationState::setAttachment(Skeleton &skeleton, Slot &slot, const String &attachmentName, bool attachments) { + slot.setAttachment( + attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot.getData().getIndex(), attachmentName)); + if (attachments) slot.setAttachmentState(_unkeyedState + Current); +} + +void AnimationState::queueEvents(TrackEntry *entry, float animationTime) { + float animationStart = entry->_animationStart, animationEnd = entry->_animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = duration != 0 ? MathUtil::fmod(entry->_trackLast, duration) : MathUtil::quietNan(); + + // Queue events before complete. + size_t i = 0, n = _events.size(); + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < trackLastWrapped) break; + if (e->_time > animationEnd) continue;// Discard events outside animation start/end. + _queue->event(entry, e); + } + + // Queue complete if completed a loop iteration or the animation. + bool complete = false; + if (entry->_loop) { + if (duration == 0) + complete = true; + else { + int cycles = (int) (entry->_trackTime / duration); + complete = cycles > 0 && cycles > (int) (entry->_trackLast / duration); + } + } else { + complete = animationTime >= animationEnd && entry->_animationLast < animationEnd; + } + if (complete) _queue->complete(entry); + + // Queue events after complete. + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < animationStart) continue;// Discard events outside animation start/end. + _queue->event(entry, e); + } +} + +void AnimationState::setCurrent(size_t index, TrackEntry *current, bool interrupt) { + TrackEntry *from = expandToIndex(index); + _tracks[index] = current; + current->_previous = NULL; + + if (from != NULL) { + if (interrupt) _queue->interrupt(from); + + current->_mixingFrom = from; + from->_mixingTo = current; + current->_mixTime = 0; + + // Store interrupted mix percentage. + if (from->_mixingFrom != NULL && from->_mixDuration > 0) { + current->_interruptAlpha *= MathUtil::min(1.0f, from->_mixTime / from->_mixDuration); + } + + from->_timelinesRotation.clear();// Reset rotation for mixing out, in case entry was mixed in. + } + + _queue->start(current);// triggers animationsChanged +} + +TrackEntry *AnimationState::expandToIndex(size_t index) { + if (index < _tracks.size()) return _tracks[index]; + while (index >= _tracks.size()) + _tracks.add(NULL); + return NULL; +} + +TrackEntry *AnimationState::newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last) { + TrackEntry *entryP = _trackEntryPool.obtain();// Pooling + TrackEntry &entry = *entryP; + + entry._trackIndex = (int) trackIndex; + entry._animation = animation; + entry._loop = loop; + entry._holdPrevious = 0; + + entry._reverse = false; + entry._shortestRotation = false; + + entry._eventThreshold = 0; + entry._alphaAttachmentThreshold = 0; + entry._mixAttachmentThreshold = 0; + entry._mixDrawOrderThreshold = 0; + + entry._animationStart = 0; + entry._animationEnd = animation->getDuration(); + entry._animationLast = -1; + entry._nextAnimationLast = -1; + + entry._delay = 0; + entry._trackTime = 0; + entry._trackLast = -1; + entry._nextTrackLast = -1;// nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet. + entry._trackEnd = FLT_MAX;// loop ? float.MaxValue : animation.Duration; + entry._timeScale = 1; + + entry._alpha = 1; + entry._mixTime = 0; + entry._mixDuration = (last == NULL) ? 0 : _data->getMix(last->_animation, animation); + entry._interruptAlpha = 1; + entry._totalAlpha = 0; + entry._mixBlend = MixBlend_Replace; + + return entryP; +} + +void AnimationState::clearNext(TrackEntry *entry) { + TrackEntry *next = entry->_next; + while (next != NULL) { + _queue->dispose(next); + next = next->_next; + } + entry->_next = NULL; +} + +void AnimationState::animationsChanged() { + _animationsChanged = false; + + _propertyIDs.clear(); + + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *entry = _tracks[i]; + if (!entry) continue; + + while (entry->_mixingFrom != NULL) + entry = entry->_mixingFrom; + + do { + if (entry->_mixingTo == NULL || entry->_mixBlend != MixBlend_Add) computeHold(entry); + entry = entry->_mixingTo; + } while (entry != NULL); + } +} + +void AnimationState::computeHold(TrackEntry *entry) { + TrackEntry *to = entry->_mixingTo; + Vector &timelines = entry->_animation->_timelines; + size_t timelinesCount = timelines.size(); + Vector &timelineMode = entry->_timelineMode; + timelineMode.setSize(timelinesCount, 0); + Vector &timelineHoldMix = entry->_timelineHoldMix; + timelineHoldMix.setSize(timelinesCount, 0); + + if (to != NULL && to->_holdPrevious) { + for (size_t i = 0; i < timelinesCount; i++) { + timelineMode[i] = _propertyIDs.addAll(timelines[i]->getPropertyIds(), true) ? HoldFirst : HoldSubsequent; + } + return; + } + + // outer: + size_t i = 0; +continue_outer: + for (; i < timelinesCount; ++i) { + Timeline *timeline = timelines[i]; + Vector &ids = timeline->getPropertyIds(); + if (!_propertyIDs.addAll(ids, true)) { + timelineMode[i] = Subsequent; + } else { + if (to == NULL || timeline->getRTTI().isExactly(AttachmentTimeline::rtti) || + timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) || + timeline->getRTTI().isExactly(EventTimeline::rtti) || !to->_animation->hasTimeline(ids)) { + timelineMode[i] = First; + } else { + for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { + if (next->_animation->hasTimeline(ids)) continue; + if (next->_mixDuration > 0) { + timelineMode[i] = HoldMix; + timelineHoldMix[i] = next; + i++; + goto continue_outer;// continue outer; + } + break; + } + timelineMode[i] = HoldFirst; + } + } + } +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/AnimationStateData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/AnimationStateData.cpp new file mode 100644 index 000000000..65719a3b3 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/AnimationStateData.cpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include + +using namespace spine; + +AnimationStateData::AnimationStateData(SkeletonData *skeletonData) : _skeletonData(skeletonData), _defaultMix(0) { +} + +void AnimationStateData::setMix(const String &fromName, const String &toName, float duration) { + Animation *from = _skeletonData->findAnimation(fromName); + Animation *to = _skeletonData->findAnimation(toName); + + setMix(from, to, duration); +} + +void AnimationStateData::setMix(Animation *from, Animation *to, float duration) { + assert(from != NULL); + assert(to != NULL); + + AnimationPair key(from, to); + _animationToMixTime.put(key, duration); +} + +float AnimationStateData::getMix(Animation *from, Animation *to) { + assert(from != NULL); + assert(to != NULL); + + AnimationPair key(from, to); + + if (_animationToMixTime.containsKey(key)) return _animationToMixTime[key]; + return _defaultMix; +} + +SkeletonData *AnimationStateData::getSkeletonData() { + return _skeletonData; +} + +float AnimationStateData::getDefaultMix() { + return _defaultMix; +} + +void AnimationStateData::setDefaultMix(float inValue) { + _defaultMix = inValue; +} + +void AnimationStateData::clear() { + _defaultMix = 0; + _animationToMixTime.clear(); +} + +AnimationStateData::AnimationPair::AnimationPair(Animation *a1, Animation *a2) : _a1(a1), _a2(a2) { +} + +bool AnimationStateData::AnimationPair::operator==(const AnimationPair &other) const { + return _a1->_name == other._a1->_name && _a2->_name == other._a2->_name; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Atlas.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Atlas.cpp new file mode 100644 index 000000000..11e24a99b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Atlas.cpp @@ -0,0 +1,350 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include + +#include + +using namespace spine; + +Atlas::Atlas(const String &path, TextureLoader *textureLoader, bool createTexture) : _textureLoader(textureLoader) { + int dirLength; + char *dir; + int length; + const char *data; + + /* Get directory from atlas path. */ + const char *lastForwardSlash = strrchr(path.buffer(), '/'); + const char *lastBackwardSlash = strrchr(path.buffer(), '\\'); + const char *lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash; + if (lastSlash == path) lastSlash++; /* Never drop starting slash. */ + dirLength = (int) (lastSlash ? lastSlash - path.buffer() : 0); + dir = SpineExtension::calloc(dirLength + 1, __FILE__, __LINE__); + memcpy(dir, path.buffer(), dirLength); + dir[dirLength] = '\0'; + + data = SpineExtension::readFile(path, &length); + if (data) { + load(data, length, dir, createTexture); + } + + SpineExtension::free(data, __FILE__, __LINE__); + SpineExtension::free(dir, __FILE__, __LINE__); +} + +Atlas::Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture) + : _textureLoader( + textureLoader) { + load(data, length, dir, createTexture); +} + +Atlas::~Atlas() { + if (_textureLoader) { + for (size_t i = 0, n = _pages.size(); i < n; ++i) { + _textureLoader->unload(_pages[i]->texture); + } + } + ContainerUtil::cleanUpVectorOfPointers(_pages); + ContainerUtil::cleanUpVectorOfPointers(_regions); +} + +void Atlas::flipV() { + for (size_t i = 0, n = _regions.size(); i < n; ++i) { + AtlasRegion *regionP = _regions[i]; + AtlasRegion ®ion = *regionP; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } +} + +AtlasRegion *Atlas::findRegion(const String &name) { + for (size_t i = 0, n = _regions.size(); i < n; ++i) + if (_regions[i]->name == name) return _regions[i]; + return NULL; +} + +Vector &Atlas::getPages() { + return _pages; +} + +Vector &Atlas::getRegions() { + return _regions; +} + +struct SimpleString { + char *start; + char *end; + int length; + + SimpleString trim() { + while (isspace((unsigned char) *start) && start < end) + start++; + if (start == end) { + length = (int) (end - start); + return *this; + } + end--; + while (((unsigned char) *end == '\r') && end >= start) + end--; + end++; + length = (int) (end - start); + return *this; + } + + int indexOf(char needle) { + char *c = start; + while (c < end) { + if (*c == needle) return (int) (c - start); + c++; + } + return -1; + } + + int indexOf(char needle, int at) { + char *c = start + at; + while (c < end) { + if (*c == needle) return (int) (c - start); + c++; + } + return -1; + } + + SimpleString substr(int s, int e) { + e = s + e; + SimpleString result; + result.start = start + s; + result.end = start + e; + result.length = e - s; + return result; + } + + SimpleString substr(int s) { + SimpleString result; + result.start = start + s; + result.end = end; + result.length = (int) (result.end - result.start); + return result; + } + + bool equals(const char *str) { + int otherLen = (int) strlen(str); + if (length != otherLen) return false; + for (int i = 0; i < length; i++) { + if (start[i] != str[i]) return false; + } + return true; + } + + char *copy() { + char *string = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(string, start, length); + string[length] = '\0'; + return string; + } + + int toInt() { + return (int) strtol(start, &end, 10); + } +}; + +struct AtlasInput { + const char *start; + const char *end; + char *index; + int length; + SimpleString line; + + AtlasInput(const char *data, int length) : start(data), end(data + length), index((char *) data), length(length) {} + + SimpleString *readLine() { + if (index >= end) return 0; + line.start = index; + while (index < end && *index != '\n') + index++; + line.end = index; + if (index != end) index++; + line = line.trim(); + line.length = (int) (end - start); + return &line; + } + + static int readEntry(SimpleString entry[5], SimpleString *line) { + if (line == NULL) return 0; + line->trim(); + if (line->length == 0) return 0; + + int colon = line->indexOf(':'); + if (colon == -1) return 0; + entry[0] = line->substr(0, colon).trim(); + for (int i = 1, lastMatch = colon + 1;; i++) { + int comma = line->indexOf(',', lastMatch); + if (comma == -1) { + entry[i] = line->substr(lastMatch).trim(); + return i; + } + entry[i] = line->substr(lastMatch, comma - lastMatch).trim(); + lastMatch = comma + 1; + if (i == 4) return 4; + } + } +}; + +int indexOf(const char **array, int count, SimpleString *str) { + for (int i = 0; i < count; i++) + if (str->equals(array[i])) return i; + return 0; +} + +void Atlas::load(const char *begin, int length, const char *dir, bool createTexture) { + static const char *formatNames[] = {"", "Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888", + "RGBA8888"}; + static const char *textureFilterNames[] = {"", "Nearest", "Linear", "MipMap", "MipMapNearestNearest", + "MipMapLinearNearest", + "MipMapNearestLinear", "MipMapLinearLinear"}; + + int dirLength = (int) strlen(dir); + int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\'; + AtlasInput reader(begin, length); + SimpleString entry[5]; + AtlasPage *page = NULL; + + SimpleString *line = reader.readLine(); + while (line != NULL && line->length == 0) + line = reader.readLine(); + + while (true) { + if (line == NULL || line->length == 0) break; + if (reader.readEntry(entry, line) == 0) break; + line = reader.readLine(); + } + + while (true) { + if (line == NULL) break; + if (line->trim().length == 0) { + page = NULL; + line = reader.readLine(); + } else if (page == NULL) { + char *name = line->copy(); + char *path = SpineExtension::calloc(dirLength + needsSlash + strlen(name) + 1, __FILE__, __LINE__); + memcpy(path, dir, dirLength); + if (needsSlash) path[dirLength] = '/'; + strcpy(path + dirLength + needsSlash, name); + page = new (__FILE__, __LINE__) AtlasPage(String(name, true)); + + while (true) { + line = reader.readLine(); + if (reader.readEntry(entry, line) == 0) break; + if (entry[0].equals("size")) { + page->width = entry[1].toInt(); + page->height = entry[2].toInt(); + } else if (entry[0].equals("format")) { + page->format = (Format) indexOf(formatNames, 8, &entry[1]); + } else if (entry[0].equals("filter")) { + page->minFilter = (TEXTURE_FILTER_ENUM) indexOf(textureFilterNames, 8, &entry[1]); + page->magFilter = (TEXTURE_FILTER_ENUM) indexOf(textureFilterNames, 8, &entry[2]); + } else if (entry[0].equals("repeat")) { + page->uWrap = TextureWrap_ClampToEdge; + page->vWrap = TextureWrap_ClampToEdge; + if (entry[1].indexOf('x') != -1) page->uWrap = TextureWrap_Repeat; + if (entry[1].indexOf('y') != -1) page->vWrap = TextureWrap_Repeat; + } else if (entry[0].equals("pma")) { + page->pma = entry[1].equals("true"); + } + } + + page->index = (int) _pages.size(); + if (createTexture && _textureLoader) _textureLoader->load(*page, String(path)); + page->texturePath = String(path, true); + _pages.add(page); + } else { + AtlasRegion *region = new (__FILE__, __LINE__) AtlasRegion(); + region->page = page; + region->rendererObject = page->texture; + region->name = String(line->copy(), true); + while (true) { + line = reader.readLine(); + int count = reader.readEntry(entry, line); + if (count == 0) break; + if (entry[0].equals("xy")) { + region->x = entry[1].toInt(); + region->y = entry[2].toInt(); + } else if (entry[0].equals("size")) { + region->width = entry[1].toInt(); + region->height = entry[2].toInt(); + } else if (entry[0].equals("bounds")) { + region->x = entry[1].toInt(); + region->y = entry[2].toInt(); + region->width = entry[3].toInt(); + region->height = entry[4].toInt(); + } else if (entry[0].equals("offset")) { + region->offsetX = entry[1].toInt(); + region->offsetY = entry[2].toInt(); + } else if (entry[0].equals("orig")) { + region->originalWidth = entry[1].toInt(); + region->originalHeight = entry[2].toInt(); + } else if (entry[0].equals("offsets")) { + region->offsetX = entry[1].toInt(); + region->offsetY = entry[2].toInt(); + region->originalWidth = entry[3].toInt(); + region->originalHeight = entry[4].toInt(); + } else if (entry[0].equals("rotate")) { + if (entry[1].equals("true")) { + region->degrees = 90; + } else if (!entry[1].equals("false")) { + region->degrees = entry[1].toInt(); + } + } else if (entry[0].equals("index")) { + region->index = entry[1].toInt(); + } else { + region->names.add(String(entry[0].copy())); + for (int i = 0; i < count; i++) { + region->values.add(entry[i + 1].toInt()); + } + } + } + if (region->originalWidth == 0 && region->originalHeight == 0) { + region->originalWidth = region->width; + region->originalHeight = region->height; + } + + region->u = (float) region->x / page->width; + region->v = (float) region->y / page->height; + if (region->degrees == 90) { + region->u2 = (float) (region->x + region->height) / page->width; + region->v2 = (float) (region->y + region->width) / page->height; + } else { + region->u2 = (float) (region->x + region->width) / page->width; + region->v2 = (float) (region->y + region->height) / page->height; + } + _regions.add(region); + } + } +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/AtlasAttachmentLoader.cpp b/Projects/SpineCpp/spine-cpp/src/spine/AtlasAttachmentLoader.cpp new file mode 100644 index 000000000..f18e5e238 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/AtlasAttachmentLoader.cpp @@ -0,0 +1,112 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace spine { + RTTI_IMPL(AtlasAttachmentLoader, AttachmentLoader) + + AtlasAttachmentLoader::AtlasAttachmentLoader(Atlas *atlas) : AttachmentLoader(), _atlas(atlas) { + } + + bool loadSequence(Atlas *atlas, const String &basePath, Sequence *sequence) { + Vector ®ions = sequence->getRegions(); + for (int i = 0, n = (int) regions.size(); i < n; i++) { + String path = sequence->getPath(basePath, i); + regions[i] = atlas->findRegion(path); + if (!regions[i]) return false; + } + return true; + } + + RegionAttachment *AtlasAttachmentLoader::newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) { + SP_UNUSED(skin); + RegionAttachment *attachment = new (__FILE__, __LINE__) RegionAttachment(name); + if (sequence) { + if (!loadSequence(_atlas, path, sequence)) return NULL; + } else { + AtlasRegion *region = findRegion(path); + if (!region) return NULL; + attachment->setRegion(region); + } + return attachment; + } + + MeshAttachment *AtlasAttachmentLoader::newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) { + SP_UNUSED(skin); + MeshAttachment *attachment = new (__FILE__, __LINE__) MeshAttachment(name); + + if (sequence) { + if (!loadSequence(_atlas, path, sequence)) return NULL; + } else { + AtlasRegion *region = findRegion(path); + if (!region) return NULL; + attachment->setRegion(region); + } + return attachment; + } + + BoundingBoxAttachment *AtlasAttachmentLoader::newBoundingBoxAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) BoundingBoxAttachment(name); + } + + PathAttachment *AtlasAttachmentLoader::newPathAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PathAttachment(name); + } + + PointAttachment *AtlasAttachmentLoader::newPointAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PointAttachment(name); + } + + ClippingAttachment *AtlasAttachmentLoader::newClippingAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) ClippingAttachment(name); + } + + void AtlasAttachmentLoader::configureAttachment(Attachment *attachment) { + SP_UNUSED(attachment); + } + + AtlasRegion *AtlasAttachmentLoader::findRegion(const String &name) { + return _atlas->findRegion(name); + } + +}// namespace spine diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Attachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Attachment.cpp new file mode 100644 index 000000000..9c2fe3e5f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Attachment.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(Attachment) + +Attachment::Attachment(const String &name) : _name(name), _refCount(0) { + assert(_name.length() > 0); +} + +Attachment::~Attachment() { +} + +const String &Attachment::getName() const { + return _name; +} + +int Attachment::getRefCount() { + return _refCount; +} + +void Attachment::reference() { + _refCount++; +} + +void Attachment::dereference() { + _refCount--; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/AttachmentLoader.cpp b/Projects/SpineCpp/spine-cpp/src/spine/AttachmentLoader.cpp new file mode 100644 index 000000000..2dc2a093e --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/AttachmentLoader.cpp @@ -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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(AttachmentLoader) + +AttachmentLoader::AttachmentLoader() { +} + +AttachmentLoader::~AttachmentLoader() { +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/AttachmentTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/AttachmentTimeline.cpp new file mode 100644 index 000000000..604b74d12 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/AttachmentTimeline.cpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(AttachmentTimeline, Timeline) + +AttachmentTimeline::AttachmentTimeline(size_t frameCount, int slotIndex) : Timeline(frameCount, 1), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Attachment << 32) | slotIndex}; + setPropertyIds(ids, 1); + + _attachmentNames.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + _attachmentNames.add(String()); + } +} + +AttachmentTimeline::~AttachmentTimeline() {} + +void AttachmentTimeline::setAttachment(Skeleton &skeleton, Slot &slot, String *attachmentName) { + slot.setAttachment(attachmentName == NULL || attachmentName->isEmpty() ? NULL : skeleton.getAttachment(_slotIndex, *attachmentName)); +} + +void AttachmentTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) setAttachment(skeleton, *slot, &slot->_data._attachmentName); + return; + } + + if (time < _frames[0]) { + // Time is before first frame. + if (blend == MixBlend_Setup || blend == MixBlend_First) { + setAttachment(skeleton, *slot, &slot->_data._attachmentName); + } + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) + setAttachment(skeleton, *slot, &slot->_data._attachmentName); + return; + } + + setAttachment(skeleton, *slot, &_attachmentNames[Animation::search(_frames, time)]); +} + +void AttachmentTimeline::setFrame(int frame, float time, const String &attachmentName) { + _frames[frame] = time; + _attachmentNames[frame] = attachmentName; +} + +Vector &AttachmentTimeline::getAttachmentNames() { + return _attachmentNames; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Bone.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Bone.cpp new file mode 100644 index 000000000..3a8597a9c --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Bone.cpp @@ -0,0 +1,595 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(Bone, Updatable) + +bool Bone::yDown = false; + +void Bone::setYDown(bool inValue) { + yDown = inValue; +} + +bool Bone::isYDown() { + return yDown; +} + +Bone::Bone(BoneData &data, Skeleton &skeleton, Bone *parent) : Updatable(), + _data(data), + _skeleton(skeleton), + _parent(parent), + _x(0), + _y(0), + _rotation(0), + _scaleX(0), + _scaleY(0), + _shearX(0), + _shearY(0), + _ax(0), + _ay(0), + _arotation(0), + _ascaleX(0), + _ascaleY(0), + _ashearX(0), + _ashearY(0), + _a(1), + _b(0), + _worldX(0), + _c(0), + _d(1), + _worldY(0), + _sorted(false), + _active(false), + _inherit(Inherit_Normal) { + setToSetupPose(); +} + +void Bone::update(Physics) { + updateWorldTransform(_ax, _ay, _arotation, _ascaleX, _ascaleY, _ashearX, _ashearY); +} + +void Bone::updateWorldTransform() { + updateWorldTransform(_x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY); +} + +void Bone::updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { + float pa, pb, pc, pd; + Bone *parent = _parent; + + _ax = x; + _ay = y; + _arotation = rotation; + _ascaleX = scaleX; + _ascaleY = scaleY; + _ashearX = shearX; + _ashearY = shearY; + + if (!parent) { /* Root bone. */ + Skeleton &skeleton = this->_skeleton; + float sx = skeleton.getScaleX(); + float sy = skeleton.getScaleY(); + float rx = (rotation + shearX) * MathUtil::Deg_Rad; + float ry = (rotation + 90 + shearY) * MathUtil::Deg_Rad; + _a = MathUtil::cos(rx) * scaleX * sx; + _b = MathUtil::cos(ry) * scaleY * sx; + _c = MathUtil::sin(rx) * scaleX * sy; + _d = MathUtil::sin(ry) * scaleY * sy; + _worldX = x * sx + _skeleton.getX(); + _worldY = y * sy + _skeleton.getY(); + return; + } + + pa = parent->_a; + pb = parent->_b; + pc = parent->_c; + pd = parent->_d; + + _worldX = pa * x + pb * y + parent->_worldX; + _worldY = pc * x + pd * y + parent->_worldY; + + switch (_inherit) { + case Inherit_Normal: { + float rx = (rotation + shearX) * MathUtil::Deg_Rad; + float ry = (rotation + 90 + shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * scaleX; + float lb = MathUtil::cos(ry) * scaleY; + float lc = MathUtil::sin(rx) * scaleX; + float ld = MathUtil::sin(ry) * scaleY; + _a = pa * la + pb * lc; + _b = pa * lb + pb * ld; + _c = pc * la + pd * lc; + _d = pc * lb + pd * ld; + return; + } + case Inherit_OnlyTranslation: { + float rx = (rotation + shearX) * MathUtil::Deg_Rad; + float ry = (rotation + 90 + shearY) * MathUtil::Deg_Rad; + _a = MathUtil::cos(rx) * scaleX; + _b = MathUtil::cos(ry) * scaleY; + _c = MathUtil::sin(rx) * scaleX; + _d = MathUtil::sin(ry) * scaleY; + break; + } + case Inherit_NoRotationOrReflection: { + float s = pa * pa + pc * pc; + float prx; + if (s > 0.0001f) { + s = MathUtil::abs(pa * pd - pb * pc) / s; + pa /= _skeleton.getScaleX(); + pc /= _skeleton.getScaleY(); + pb = pc * s; + pd = pa * s; + prx = MathUtil::atan2Deg(pc, pa); + } else { + pa = 0; + pc = 0; + prx = 90 - MathUtil::atan2Deg(pd, pb); + } + float rx = (rotation + shearX - prx) * MathUtil::Deg_Rad; + float ry = (rotation + shearY - prx + 90) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * scaleX; + float lb = MathUtil::cos(ry) * scaleY; + float lc = MathUtil::sin(rx) * scaleX; + float ld = MathUtil::sin(ry) * scaleY; + _a = pa * la - pb * lc; + _b = pa * lb - pb * ld; + _c = pc * la + pd * lc; + _d = pc * lb + pd * ld; + break; + } + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: { + rotation *= MathUtil::Deg_Rad; + float cosine = MathUtil::cos(rotation); + float sine = MathUtil::sin(rotation); + float za = (pa * cosine + pb * sine) / _skeleton.getScaleX(); + float zc = (pc * cosine + pd * sine) / _skeleton.getScaleY(); + float s = MathUtil::sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = MathUtil::sqrt(za * za + zc * zc); + if (_inherit == Inherit_NoScale && + (pa * pd - pb * pc < 0) != (_skeleton.getScaleX() < 0 != _skeleton.getScaleY() < 0)) + s = -s; + rotation = MathUtil::Pi / 2 + MathUtil::atan2(zc, za); + float zb = MathUtil::cos(rotation) * s; + float zd = MathUtil::sin(rotation) * s; + shearX *= MathUtil::Deg_Rad; + shearY = (90 + shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(shearX) * scaleX; + float lb = MathUtil::cos(shearY) * scaleY; + float lc = MathUtil::sin(shearX) * scaleX; + float ld = MathUtil::sin(shearY) * scaleY; + _a = za * la + zb * lc; + _b = za * lb + zb * ld; + _c = zc * la + zd * lc; + _d = zc * lb + zd * ld; + } + } + _a *= _skeleton.getScaleX(); + _b *= _skeleton.getScaleX(); + _c *= _skeleton.getScaleY(); + _d *= _skeleton.getScaleY(); +} + +void Bone::setToSetupPose() { + BoneData &data = _data; + _x = data.getX(); + _y = data.getY(); + _rotation = data.getRotation(); + _scaleX = data.getScaleX(); + _scaleY = data.getScaleY(); + _shearX = data.getShearX(); + _shearY = data.getShearY(); + _inherit = data.getInherit(); +} + +void Bone::worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY) { + float a = _a; + float b = _b; + float c = _c; + float d = _d; + + float invDet = 1 / (a * d - b * c); + float x = worldX - _worldX; + float y = worldY - _worldY; + + outLocalX = (x * d * invDet - y * b * invDet); + outLocalY = (y * a * invDet - x * c * invDet); +} + +void Bone::worldToParent(float worldX, float worldY, float &outParentX, float &outParentY) { + if (!_parent) { + outParentX = worldX; + outParentY = worldY; + } else { + _parent->worldToLocal(worldX, worldY, outParentX, outParentY); + } +} + +void Bone::localToWorld(float localX, float localY, float &outWorldX, float &outWorldY) { + outWorldX = localX * _a + localY * _b + _worldX; + outWorldY = localX * _c + localY * _d + _worldY; +} + +void Bone::parentToWorld(float worldX, float worldY, float &outX, float &outY) { + if (!_parent) { + outX = worldX; + outY = worldY; + } else { + _parent->localToWorld(worldX, worldY, outX, outY); + } +} + +float Bone::worldToLocalRotation(float worldRotation) { + worldRotation *= MathUtil::Deg_Rad; + float sine = MathUtil::sin(worldRotation), cosine = MathUtil::cos(worldRotation); + return MathUtil::atan2Deg(_a * sine - _c * cosine, _d * cosine - _b * sine) + _rotation - _shearX; +} + +float Bone::localToWorldRotation(float localRotation) { + localRotation = (localRotation - _rotation - _shearX) * MathUtil::Deg_Rad; + float sine = MathUtil::sin(localRotation), cosine = MathUtil::cos(localRotation); + return MathUtil::atan2Deg(cosine * _c + sine * _d, cosine * _a + sine * _b); +} + +void Bone::rotateWorld(float degrees) { + degrees *= MathUtil::Deg_Rad; + float sine = MathUtil::sin(degrees), cosine = MathUtil::cos(degrees); + float ra = _a, rb = _b; + _a = cosine * ra - sine * _c; + _b = cosine * rb - sine * _d; + _c = sine * ra + cosine * _c; + _d = sine * rb + cosine * _d; +} + +float Bone::getWorldToLocalRotationX() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float a = _a; + float c = _c; + + return MathUtil::atan2(pa * c - pc * a, pd * a - pb * c) * MathUtil::Rad_Deg; +} + +float Bone::getWorldToLocalRotationY() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float b = _b; + float d = _d; + + return MathUtil::atan2(pa * d - pc * b, pd * b - pb * d) * MathUtil::Rad_Deg; +} + +BoneData &Bone::getData() { + return _data; +} + +Skeleton &Bone::getSkeleton() { + return _skeleton; +} + +Bone *Bone::getParent() { + return _parent; +} + +Vector &Bone::getChildren() { + return _children; +} + +float Bone::getX() { + return _x; +} + +void Bone::setX(float inValue) { + _x = inValue; +} + +float Bone::getY() { + return _y; +} + +void Bone::setY(float inValue) { + _y = inValue; +} + +float Bone::getRotation() { + return _rotation; +} + +void Bone::setRotation(float inValue) { + _rotation = inValue; +} + +float Bone::getScaleX() { + return _scaleX; +} + +void Bone::setScaleX(float inValue) { + _scaleX = inValue; +} + +float Bone::getScaleY() { + return _scaleY; +} + +void Bone::setScaleY(float inValue) { + _scaleY = inValue; +} + +float Bone::getShearX() { + return _shearX; +} + +void Bone::setShearX(float inValue) { + _shearX = inValue; +} + +float Bone::getShearY() { + return _shearY; +} + +void Bone::setShearY(float inValue) { + _shearY = inValue; +} + +float Bone::getAppliedRotation() { + return _arotation; +} + +void Bone::setAppliedRotation(float inValue) { + _arotation = inValue; +} + +float Bone::getAX() { + return _ax; +} + +void Bone::setAX(float inValue) { + _ax = inValue; +} + +float Bone::getAY() { + return _ay; +} + +void Bone::setAY(float inValue) { + _ay = inValue; +} + +float Bone::getAScaleX() { + return _ascaleX; +} + +void Bone::setAScaleX(float inValue) { + _ascaleX = inValue; +} + +float Bone::getAScaleY() { + return _ascaleY; +} + +void Bone::setAScaleY(float inValue) { + _ascaleY = inValue; +} + +float Bone::getAShearX() { + return _ashearX; +} + +void Bone::setAShearX(float inValue) { + _ashearX = inValue; +} + +float Bone::getAShearY() { + return _ashearY; +} + +void Bone::setAShearY(float inValue) { + _ashearY = inValue; +} + +float Bone::getA() { + return _a; +} + +void Bone::setA(float inValue) { + _a = inValue; +} + +float Bone::getB() { + return _b; +} + +void Bone::setB(float inValue) { + _b = inValue; +} + +float Bone::getC() { + return _c; +} + +void Bone::setC(float inValue) { + _c = inValue; +} + +float Bone::getD() { + return _d; +} + +void Bone::setD(float inValue) { + _d = inValue; +} + +float Bone::getWorldX() { + return _worldX; +} + +void Bone::setWorldX(float inValue) { + _worldX = inValue; +} + +float Bone::getWorldY() { + return _worldY; +} + +void Bone::setWorldY(float inValue) { + _worldY = inValue; +} + +float Bone::getWorldRotationX() { + return MathUtil::atan2Deg(_c, _a); +} + +float Bone::getWorldRotationY() { + return MathUtil::atan2Deg(_d, _b); +} + +float Bone::getWorldScaleX() { + return MathUtil::sqrt(_a * _a + _c * _c); +} + +float Bone::getWorldScaleY() { + return MathUtil::sqrt(_b * _b + _d * _d); +} + +void Bone::updateAppliedTransform() { + Bone *parent = _parent; + if (!parent) { + _ax = _worldX - _skeleton.getX(); + _ay = _worldY - _skeleton.getY(); + _arotation = MathUtil::atan2Deg(_c, _a); + _ascaleX = MathUtil::sqrt(_a * _a + _c * _c); + _ascaleY = MathUtil::sqrt(_b * _b + _d * _d); + _ashearX = 0; + _ashearY = MathUtil::atan2Deg(_a * _b + _c * _d, _a * _d - _b * _c); + } + float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d; + float pid = 1 / (pa * pd - pb * pc); + float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; + float dx = _worldX - parent->_worldX, dy = _worldY - parent->_worldY; + _ax = (dx * ia - dy * ib); + _ay = (dy * id - dx * ic); + + float ra, rb, rc, rd; + if (_inherit == Inherit_OnlyTranslation) { + ra = _a; + rb = _b; + rc = _c; + rd = _d; + } else { + switch (_inherit) { + case Inherit_NoRotationOrReflection: { + float s = MathUtil::abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / _skeleton.getScaleX(); + float sc = pc / _skeleton.getScaleY(); + pb = -sc * s * _skeleton.getScaleX(); + pd = sa * s * _skeleton.getScaleY(); + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + break; + } + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: { + float r = _rotation * MathUtil::Deg_Rad; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + pa = (pa * cos + pb * sin) / _skeleton.getScaleX(); + pc = (pc * cos + pd * sin) / _skeleton.getScaleY(); + float s = MathUtil::sqrt(pa * pa + pc * pc); + if (s > 0.00001) s = 1 / s; + pa *= s; + pc *= s; + s = MathUtil::sqrt(pa * pa + pc * pc); + if (_inherit == Inherit_NoScale && + pid < 0 != (_skeleton.getScaleX() < 0 != _skeleton.getScaleY() < 0)) + s = -s; + r = MathUtil::Pi / 2 + MathUtil::atan2(pc, pa); + pb = MathUtil::cos(r) * s; + pd = MathUtil::sin(r) * s; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + ic = pc * pid; + id = pa * pid; + break; + } + case Inherit_Normal: + case Inherit_OnlyTranslation: + break; + } + ra = ia * _a - ib * _c; + rb = ia * _b - ib * _d; + rc = id * _c - ic * _a; + rd = id * _d - ic * _b; + } + + _ashearX = 0; + _ascaleX = MathUtil::sqrt(ra * ra + rc * rc); + if (_ascaleX > 0.0001f) { + float det = ra * rd - rb * rc; + _ascaleY = det / _ascaleX; + _ashearY = -MathUtil::atan2Deg(ra * rb + rc * rd, det); + _arotation = MathUtil::atan2Deg(rc, ra); + } else { + _ascaleX = 0; + _ascaleY = MathUtil::sqrt(rb * rb + rd * rd); + _ashearY = 0; + _arotation = 90 - MathUtil::atan2Deg(rd, rb); + } +} + +bool Bone::isActive() { + return _active; +} + +void Bone::setActive(bool inValue) { + _active = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/BoneData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/BoneData.cpp new file mode 100644 index 000000000..e6983463f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/BoneData.cpp @@ -0,0 +1,166 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +BoneData::BoneData(int index, const String &name, BoneData *parent) : _index(index), + _name(name), + _parent(parent), + _length(0), + _x(0), + _y(0), + _rotation(0), + _scaleX(1), + _scaleY(1), + _shearX(0), + _shearY(0), + _inherit(Inherit_Normal), + _skinRequired(false), + _color(), + _icon(), + _visible(true) { + assert(index >= 0); + assert(_name.length() > 0); +} + +int BoneData::getIndex() { + return _index; +} + +const String &BoneData::getName() { + return _name; +} + +BoneData *BoneData::getParent() { + return _parent; +} + +float BoneData::getLength() { + return _length; +} + +void BoneData::setLength(float inValue) { + _length = inValue; +} + +float BoneData::getX() { + return _x; +} + +void BoneData::setX(float inValue) { + _x = inValue; +} + +float BoneData::getY() { + return _y; +} + +void BoneData::setY(float inValue) { + _y = inValue; +} + +float BoneData::getRotation() { + return _rotation; +} + +void BoneData::setRotation(float inValue) { + _rotation = inValue; +} + +float BoneData::getScaleX() { + return _scaleX; +} + +void BoneData::setScaleX(float inValue) { + _scaleX = inValue; +} + +float BoneData::getScaleY() { + return _scaleY; +} + +void BoneData::setScaleY(float inValue) { + _scaleY = inValue; +} + +float BoneData::getShearX() { + return _shearX; +} + +void BoneData::setShearX(float inValue) { + _shearX = inValue; +} + +float BoneData::getShearY() { + return _shearY; +} + +void BoneData::setShearY(float inValue) { + _shearY = inValue; +} + +Inherit BoneData::getInherit() { + return _inherit; +} + +void BoneData::setInherit(Inherit inValue) { + _inherit = inValue; +} + +bool BoneData::isSkinRequired() { + return _skinRequired; +} + +void BoneData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} + +Color &BoneData::getColor() { + return _color; +} + +const String &BoneData::getIcon() { + return _icon; +} + +void BoneData::setIcon(const String &icon) { + this->_icon = icon; +} + +bool BoneData::isVisible() { + return _visible; +} + +void BoneData::setVisible(bool inValue) { + this->_visible = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/BoundingBoxAttachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/BoundingBoxAttachment.cpp new file mode 100644 index 000000000..758113cc5 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/BoundingBoxAttachment.cpp @@ -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. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL(BoundingBoxAttachment, VertexAttachment) + +BoundingBoxAttachment::BoundingBoxAttachment(const String &name) : VertexAttachment(name), _color() { +} + +Color &BoundingBoxAttachment::getColor() { + return _color; +} + +Attachment *BoundingBoxAttachment::copy() { + BoundingBoxAttachment *copy = new (__FILE__, __LINE__) BoundingBoxAttachment(getName()); + copyTo(copy); + return copy; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/ClippingAttachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/ClippingAttachment.cpp new file mode 100644 index 000000000..fa9e65cd0 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/ClippingAttachment.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL(ClippingAttachment, VertexAttachment) + +ClippingAttachment::ClippingAttachment(const String &name) : VertexAttachment(name), _endSlot(NULL), _color() { +} + +SlotData *ClippingAttachment::getEndSlot() { + return _endSlot; +} + +void ClippingAttachment::setEndSlot(SlotData *inValue) { + _endSlot = inValue; +} + +Color &ClippingAttachment::getColor() { + return _color; +} + +Attachment *ClippingAttachment::copy() { + ClippingAttachment *copy = new (__FILE__, __LINE__) ClippingAttachment(getName()); + copyTo(copy); + copy->_endSlot = _endSlot; + return copy; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/ColorTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/ColorTimeline.cpp new file mode 100644 index 000000000..f804c562d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/ColorTimeline.cpp @@ -0,0 +1,493 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(RGBATimeline, CurveTimeline) + +RGBATimeline::RGBATimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBATimeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Alpha << 32) | slotIndex}; + setPropertyIds(ids, 2); +} + +RGBATimeline::~RGBATimeline() { +} + +void RGBATimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.set(setup); + return; + case MixBlend_First: + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, a = 0; + int i = Animation::search(_frames, time, RGBATimeline::ENTRIES); + int curveType = (int) _curves[i / RGBATimeline::ENTRIES]; + switch (curveType) { + case RGBATimeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBATimeline::R]; + g = _frames[i + RGBATimeline::G]; + b = _frames[i + RGBATimeline::B]; + a = _frames[i + RGBATimeline::A]; + float t = (time - before) / (_frames[i + RGBATimeline::ENTRIES] - before); + r += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::R] - r) * t; + g += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::G] - g) * t; + b += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::B] - b) * t; + a += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::A] - a) * t; + break; + } + case RGBATimeline::STEPPED: { + r = _frames[i + RGBATimeline::R]; + g = _frames[i + RGBATimeline::G]; + b = _frames[i + RGBATimeline::B]; + a = _frames[i + RGBATimeline::A]; + break; + } + default: { + r = getBezierValue(time, i, RGBATimeline::R, curveType - RGBATimeline::BEZIER); + g = getBezierValue(time, i, RGBATimeline::G, + curveType + RGBATimeline::BEZIER_SIZE - RGBATimeline::BEZIER); + b = getBezierValue(time, i, RGBATimeline::B, + curveType + RGBATimeline::BEZIER_SIZE * 2 - RGBATimeline::BEZIER); + a = getBezierValue(time, i, RGBATimeline::A, + curveType + RGBATimeline::BEZIER_SIZE * 3 - RGBATimeline::BEZIER); + } + } + Color &color = slot->_color; + if (alpha == 1) + color.set(r, g, b, a); + else { + if (blend == MixBlend_Setup) color.set(slot->_data._color); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + } +} + +void RGBATimeline::setFrame(int frame, float time, float r, float g, float b, float a) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; + _frames[frame + A] = a; +} + +RTTI_IMPL(RGBTimeline, CurveTimeline) + +RGBTimeline::RGBTimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBTimeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex}; + setPropertyIds(ids, 1); +} + +RGBTimeline::~RGBTimeline() { +} + +void RGBTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.set(setup); + return; + case MixBlend_First: + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0; + int i = Animation::search(_frames, time, RGBTimeline::ENTRIES); + int curveType = (int) _curves[i / RGBTimeline::ENTRIES]; + switch (curveType) { + case RGBTimeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBTimeline::R]; + g = _frames[i + RGBTimeline::G]; + b = _frames[i + RGBTimeline::B]; + float t = (time - before) / (_frames[i + RGBTimeline::ENTRIES] - before); + r += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::R] - r) * t; + g += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::G] - g) * t; + b += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::B] - b) * t; + break; + } + case RGBTimeline::STEPPED: { + r = _frames[i + RGBTimeline::R]; + g = _frames[i + RGBTimeline::G]; + b = _frames[i + RGBTimeline::B]; + break; + } + default: { + r = getBezierValue(time, i, RGBTimeline::R, curveType - RGBTimeline::BEZIER); + g = getBezierValue(time, i, RGBTimeline::G, + curveType + RGBTimeline::BEZIER_SIZE - RGBTimeline::BEZIER); + b = getBezierValue(time, i, RGBTimeline::B, + curveType + RGBTimeline::BEZIER_SIZE * 2 - RGBTimeline::BEZIER); + } + } + Color &color = slot->_color; + if (alpha == 1) + color.set(r, g, b); + else { + Color &setup = slot->_data._color; + if (blend == MixBlend_Setup) color.set(setup.r, setup.g, setup.b); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha); + } +} + +void RGBTimeline::setFrame(int frame, float time, float r, float g, float b) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; +} + +RTTI_IMPL(AlphaTimeline, CurveTimeline1) + +AlphaTimeline::AlphaTimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline1(frameCount, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Alpha << 32) | slotIndex}; + setPropertyIds(ids, 1); +} + +AlphaTimeline::~AlphaTimeline() { +} + +void AlphaTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) {// Time is before first frame. + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.a = setup.a; + return; + case MixBlend_First: + color.a += (setup.a - color.a) * alpha; + default: { + } + } + return; + } + + float a = getCurveValue(time); + if (alpha == 1) + slot->_color.a = a; + else { + if (blend == MixBlend_Setup) slot->_color.a = slot->_data._color.a; + slot->_color.a += (a - slot->_color.a) * alpha; + } +} + +RTTI_IMPL(RGBA2Timeline, CurveTimeline) + +RGBA2Timeline::RGBA2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBA2Timeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Alpha << 32) | slotIndex, + ((PropertyId) Property_Rgb2 << 32) | slotIndex}; + setPropertyIds(ids, 3); +} + +RGBA2Timeline::~RGBA2Timeline() { +} + +void RGBA2Timeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &light = slot->_color, &dark = slot->_darkColor, &setupLight = slot->_data._color, &setupDark = slot->_data._darkColor; + switch (blend) { + case MixBlend_Setup: + light.set(setupLight); + dark.set(setupDark.r, setupDark.g, setupDark.b); + return; + case MixBlend_First: + light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, + (setupLight.b - light.b) * alpha, + (setupLight.a - light.a) * alpha); + dark.r += (setupDark.r - dark.r) * alpha; + dark.g += (setupDark.g - dark.g) * alpha; + dark.b += (setupDark.b - dark.b) * alpha; + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0; + int i = Animation::search(_frames, time, RGBA2Timeline::ENTRIES); + int curveType = (int) _curves[i / RGBA2Timeline::ENTRIES]; + switch (curveType) { + case RGBA2Timeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBA2Timeline::R]; + g = _frames[i + RGBA2Timeline::G]; + b = _frames[i + RGBA2Timeline::B]; + a = _frames[i + RGBA2Timeline::A]; + r2 = _frames[i + RGBA2Timeline::R2]; + g2 = _frames[i + RGBA2Timeline::G2]; + b2 = _frames[i + RGBA2Timeline::B2]; + float t = (time - before) / (_frames[i + RGBA2Timeline::ENTRIES] - before); + r += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::R] - r) * t; + g += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::G] - g) * t; + b += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::B] - b) * t; + a += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::A] - a) * t; + r2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::R2] - r2) * t; + g2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::G2] - g2) * t; + b2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::B2] - b2) * t; + break; + } + case RGBA2Timeline::STEPPED: { + r = _frames[i + RGBA2Timeline::R]; + g = _frames[i + RGBA2Timeline::G]; + b = _frames[i + RGBA2Timeline::B]; + a = _frames[i + RGBA2Timeline::A]; + r2 = _frames[i + RGBA2Timeline::R2]; + g2 = _frames[i + RGBA2Timeline::G2]; + b2 = _frames[i + RGBA2Timeline::B2]; + break; + } + default: { + r = getBezierValue(time, i, RGBA2Timeline::R, curveType - RGBA2Timeline::BEZIER); + g = getBezierValue(time, i, RGBA2Timeline::G, + curveType + RGBA2Timeline::BEZIER_SIZE - RGBA2Timeline::BEZIER); + b = getBezierValue(time, i, RGBA2Timeline::B, + curveType + RGBA2Timeline::BEZIER_SIZE * 2 - RGBA2Timeline::BEZIER); + a = getBezierValue(time, i, RGBA2Timeline::A, + curveType + RGBA2Timeline::BEZIER_SIZE * 3 - RGBA2Timeline::BEZIER); + r2 = getBezierValue(time, i, RGBA2Timeline::R2, + curveType + RGBA2Timeline::BEZIER_SIZE * 4 - RGBA2Timeline::BEZIER); + g2 = getBezierValue(time, i, RGBA2Timeline::G2, + curveType + RGBA2Timeline::BEZIER_SIZE * 5 - RGBA2Timeline::BEZIER); + b2 = getBezierValue(time, i, RGBA2Timeline::B2, + curveType + RGBA2Timeline::BEZIER_SIZE * 6 - RGBA2Timeline::BEZIER); + } + } + Color &light = slot->_color, &dark = slot->_darkColor; + if (alpha == 1) { + light.set(r, g, b, a); + dark.set(r2, g2, b2); + } else { + if (blend == MixBlend_Setup) { + light.set(slot->_data._color); + dark.set(slot->_data._darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + dark.r += (r2 - dark.r) * alpha; + dark.g += (g2 - dark.g) * alpha; + dark.b += (b2 - dark.b) * alpha; + } +} + +void RGBA2Timeline::setFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; + _frames[frame + A] = a; + _frames[frame + R2] = r2; + _frames[frame + G2] = g2; + _frames[frame + B2] = b2; +} + +RTTI_IMPL(RGB2Timeline, CurveTimeline) + +RGB2Timeline::RGB2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGB2Timeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Rgb2 << 32) | slotIndex}; + setPropertyIds(ids, 2); +} + +RGB2Timeline::~RGB2Timeline() { +} + +void RGB2Timeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &light = slot->_color, &dark = slot->_darkColor, &setupLight = slot->_data._color, &setupDark = slot->_data._darkColor; + switch (blend) { + case MixBlend_Setup: + light.set(setupLight.r, setupLight.g, setupLight.b); + dark.set(setupDark.r, setupDark.g, setupDark.b); + return; + case MixBlend_First: + light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, + (setupLight.b - light.b) * alpha); + dark.r += (setupDark.r - dark.r) * alpha; + dark.g += (setupDark.g - dark.g) * alpha; + dark.b += (setupDark.b - dark.b) * alpha; + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0; + int i = Animation::search(_frames, time, RGB2Timeline::ENTRIES); + int curveType = (int) _curves[i / RGB2Timeline::ENTRIES]; + switch (curveType) { + case RGB2Timeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGB2Timeline::R]; + g = _frames[i + RGB2Timeline::G]; + b = _frames[i + RGB2Timeline::B]; + r2 = _frames[i + RGB2Timeline::R2]; + g2 = _frames[i + RGB2Timeline::G2]; + b2 = _frames[i + RGB2Timeline::B2]; + float t = (time - before) / (_frames[i + RGB2Timeline::ENTRIES] - before); + r += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::R] - r) * t; + g += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::G] - g) * t; + b += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::B] - b) * t; + r2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::R2] - r2) * t; + g2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::G2] - g2) * t; + b2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::B2] - b2) * t; + break; + } + case RGB2Timeline::STEPPED: { + r = _frames[i + RGB2Timeline::R]; + g = _frames[i + RGB2Timeline::G]; + b = _frames[i + RGB2Timeline::B]; + r2 = _frames[i + RGB2Timeline::R2]; + g2 = _frames[i + RGB2Timeline::G2]; + b2 = _frames[i + RGB2Timeline::B2]; + break; + } + default: { + r = getBezierValue(time, i, RGB2Timeline::R, curveType - RGB2Timeline::BEZIER); + g = getBezierValue(time, i, RGB2Timeline::G, + curveType + RGB2Timeline::BEZIER_SIZE - RGB2Timeline::BEZIER); + b = getBezierValue(time, i, RGB2Timeline::B, + curveType + RGB2Timeline::BEZIER_SIZE * 2 - RGB2Timeline::BEZIER); + r2 = getBezierValue(time, i, RGB2Timeline::R2, + curveType + RGB2Timeline::BEZIER_SIZE * 4 - RGB2Timeline::BEZIER); + g2 = getBezierValue(time, i, RGB2Timeline::G2, + curveType + RGB2Timeline::BEZIER_SIZE * 5 - RGB2Timeline::BEZIER); + b2 = getBezierValue(time, i, RGB2Timeline::B2, + curveType + RGB2Timeline::BEZIER_SIZE * 6 - RGB2Timeline::BEZIER); + } + } + Color &light = slot->_color, &dark = slot->_darkColor; + if (alpha == 1) { + light.set(r, g, b); + dark.set(r2, g2, b2); + } else { + if (blend == MixBlend_Setup) { + light.set(slot->_data._color.r, slot->_data._color.g, slot->_data._color.b); + dark.set(slot->_data._darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha); + dark.r += (r2 - dark.r) * alpha; + dark.g += (g2 - dark.g) * alpha; + dark.b += (b2 - dark.b) * alpha; + } +} + +void RGB2Timeline::setFrame(int frame, float time, float r, float g, float b, float r2, float g2, float b2) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; + _frames[frame + R2] = r2; + _frames[frame + G2] = g2; + _frames[frame + B2] = b2; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/ConstraintData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/ConstraintData.cpp new file mode 100644 index 000000000..e60872eca --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/ConstraintData.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(ConstraintData) + +ConstraintData::ConstraintData(const String &name) : _name(name), _order(0), _skinRequired(false) { +} + +ConstraintData::~ConstraintData() { +} + +const String &ConstraintData::getName() { + return _name; +} + +size_t ConstraintData::getOrder() { + return _order; +} + +void ConstraintData::setOrder(size_t inValue) { + _order = inValue; +} + +bool ConstraintData::isSkinRequired() { + return _skinRequired; +} + +void ConstraintData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/CurveTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/CurveTimeline.cpp new file mode 100644 index 000000000..a26f567a9 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/CurveTimeline.cpp @@ -0,0 +1,252 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL(CurveTimeline, Timeline) + +CurveTimeline::CurveTimeline(size_t frameCount, size_t frameEntries, size_t bezierCount) : Timeline(frameCount, + frameEntries) { + _curves.setSize(frameCount + bezierCount * BEZIER_SIZE, 0); + _curves[frameCount - 1] = STEPPED; +} + +CurveTimeline::~CurveTimeline() { +} + +void CurveTimeline::setLinear(size_t frame) { + _curves[frame] = LINEAR; +} + +void CurveTimeline::setStepped(size_t frame) { + _curves[frame] = STEPPED; +} + +void CurveTimeline::setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, + float cx2, float cy2, float time2, float value2) { + size_t i = getFrameCount() + bezier * BEZIER_SIZE; + if (value == 0) _curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = (value1 - cy1 * 2 + cy2) * 0.03; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667; + float x = time1 + dx, y = value1 + dy; + for (size_t n = i + BEZIER_SIZE; i < n; i += 2) { + _curves[i] = x; + _curves[i + 1] = y; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; + x += dx; + y += dy; + } +} + +float CurveTimeline::getBezierValue(float time, size_t frameIndex, size_t valueOffset, size_t i) { + if (_curves[i] > time) { + float x = _frames[frameIndex], y = _frames[frameIndex + valueOffset]; + return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y); + } + size_t n = i + BEZIER_SIZE; + for (i += 2; i < n; i += 2) { + if (_curves[i] >= time) { + float x = _curves[i - 2], y = _curves[i - 1]; + return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y); + } + } + frameIndex += getFrameEntries(); + float x = _curves[n - 2], y = _curves[n - 1]; + return y + (time - x) / (_frames[frameIndex] - x) * (_frames[frameIndex + valueOffset] - y); +} + +Vector &CurveTimeline::getCurves() { + return _curves; +} + +RTTI_IMPL(CurveTimeline1, CurveTimeline) + +CurveTimeline1::CurveTimeline1(size_t frameCount, size_t bezierCount) : CurveTimeline(frameCount, + CurveTimeline1::ENTRIES, + bezierCount) { +} + +CurveTimeline1::~CurveTimeline1() { +} + +void CurveTimeline1::setFrame(size_t frame, float time, float value) { + frame <<= 1; + _frames[frame] = time; + _frames[frame + CurveTimeline1::VALUE] = value; +} + +float CurveTimeline1::getCurveValue(float time) { + int i = (int) _frames.size() - 2; + for (int ii = 2; ii <= i; ii += 2) { + if (_frames[ii] > time) { + i = ii - 2; + break; + } + } + + int curveType = (int) _curves[i >> 1]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i], value = _frames[i + CurveTimeline1::VALUE]; + return value + (time - before) / (_frames[i + CurveTimeline1::ENTRIES] - before) * + (_frames[i + CurveTimeline1::ENTRIES + CurveTimeline1::VALUE] - value); + } + case CurveTimeline::STEPPED: + return _frames[i + CurveTimeline1::VALUE]; + } + return getBezierValue(time, i, CurveTimeline1::VALUE, curveType - CurveTimeline1::BEZIER); +} + +float CurveTimeline1::getRelativeValue(float time, float alpha, MixBlend blend, float current, float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time); + switch (blend) { + case MixBlend_Setup: + return setup + value * alpha; + case MixBlend_First: + case MixBlend_Replace: + value += setup - current; + break; + case MixBlend_Add: + break; + } + return current + value * alpha; +} + +float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time); + if (blend == MixBlend_Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; +} + +float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup, float value) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + if (blend == MixBlend_Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; +} + +float CurveTimeline1::getScaleValue(float time, float alpha, MixBlend blend, MixDirection direction, float current, + float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time) * setup; + if (alpha == 1) { + if (blend == MixBlend_Add) return current + value - setup; + return value; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (direction == MixDirection_Out) { + switch (blend) { + case MixBlend_Setup: + return setup + (MathUtil::abs(value) * MathUtil::sign(setup) - setup) * alpha; + case MixBlend_First: + case MixBlend_Replace: + return current + (MathUtil::abs(value) * MathUtil::sign(current) - current) * alpha; + default: + break; + } + } else { + float s; + switch (blend) { + case MixBlend_Setup: + s = MathUtil::abs(setup) * MathUtil::sign(value); + return s + (value - s) * alpha; + case MixBlend_First: + case MixBlend_Replace: + s = MathUtil::abs(current) * MathUtil::sign(value); + return s + (value - s) * alpha; + default: + break; + } + } + return current + (value - setup) * alpha; +} + + +RTTI_IMPL(CurveTimeline2, CurveTimeline) + +CurveTimeline2::CurveTimeline2(size_t frameCount, size_t bezierCount) : CurveTimeline(frameCount, + CurveTimeline2::ENTRIES, + bezierCount) { +} + +CurveTimeline2::~CurveTimeline2() { +} + +void CurveTimeline2::setFrame(size_t frame, float time, float value1, float value2) { + frame *= CurveTimeline2::ENTRIES; + _frames[frame] = time; + _frames[frame + CurveTimeline2::VALUE1] = value1; + _frames[frame + CurveTimeline2::VALUE2] = value2; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/DeformTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/DeformTimeline.cpp new file mode 100644 index 000000000..9f0cc4d07 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/DeformTimeline.cpp @@ -0,0 +1,328 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DeformTimeline, CurveTimeline) + +DeformTimeline::DeformTimeline(size_t frameCount, size_t bezierCount, int slotIndex, VertexAttachment *attachment) + : CurveTimeline(frameCount, 1, bezierCount), _slotIndex(slotIndex), _attachment(attachment) { + PropertyId ids[] = {((PropertyId) Property_Deform << 32) | ((slotIndex << 16 | attachment->_id) & 0xffffffff)}; + setPropertyIds(ids, 1); + + _vertices.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + Vector vec; + _vertices.add(vec); + } +} + +void DeformTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slotP = skeleton._slots[_slotIndex]; + Slot &slot = *slotP; + if (!slot._bone.isActive()) return; + + Attachment *slotAttachment = slot.getAttachment(); + if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti)) { + return; + } + + VertexAttachment *attachment = static_cast(slotAttachment); + if (attachment->_timelineAttachment != _attachment) { + return; + } + + Vector &deformArray = slot._deform; + if (deformArray.size() == 0) { + blend = MixBlend_Setup; + } + + Vector> &vertices = _vertices; + size_t vertexCount = vertices[0].size(); + + Vector &frames = _frames; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + deformArray.clear(); + return; + case MixBlend_First: { + if (alpha == 1) { + deformArray.clear(); + return; + } + deformArray.setSize(vertexCount, 0); + Vector &deform = deformArray; + if (attachment->getBones().size() == 0) { + // Unweighted vertex positions. + Vector &setupVertices = attachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } else { + // Weighted deform offsets. + alpha = 1 - alpha; + for (size_t i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + } + case MixBlend_Replace: + case MixBlend_Add: { + } + } + return; + } + + deformArray.setSize(vertexCount, 0); + Vector &deform = deformArray; + + if (time >= frames[frames.size() - 1]) {// Time is after last frame. + Vector &lastVertices = vertices[frames.size() - 1]; + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } else { + // Vertex positions or deform offsets, no alpha. + memcpy(deform.buffer(), lastVertices.buffer(), vertexCount * sizeof(float)); + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] = lastVertices[i] * alpha; + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } else { + // Weighted deform offsets, alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation::search(frames, time); + float percent = getCurvePercent(time, frame); + Vector &prevVertices = vertices[frame]; + Vector &nextVertices = vertices[frame + 1]; + + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; + } + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent; + } + } + } else { + // Vertex positions or deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i], setup = setupVertices[i]; + deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + } +} + +void DeformTimeline::setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, + float cx2, float cy2, float time2, float value2) { + SP_UNUSED(value1); + SP_UNUSED(value2); + size_t i = getFrameCount() + bezier * DeformTimeline::BEZIER_SIZE; + if (value == 0) _curves[frame] = DeformTimeline::BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = cy2 * 0.03 - cy1 * 0.06; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = (cy1 - cy2 + 0.33333333) * 0.018; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = cy1 * 0.3 + tmpy + dddy * 0.16666667; + float x = time1 + dx, y = dy; + for (size_t n = i + DeformTimeline::BEZIER_SIZE; i < n; i += 2) { + _curves[i] = x; + _curves[i + 1] = y; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; + x += dx; + y += dy; + } +} + +float DeformTimeline::getCurvePercent(float time, int frame) { + int i = (int) _curves[frame]; + switch (i) { + case DeformTimeline::LINEAR: { + float x = _frames[frame]; + return (time - x) / (_frames[frame + getFrameEntries()] - x); + } + case DeformTimeline::STEPPED: { + return 0; + } + default: { + } + } + i -= DeformTimeline::BEZIER; + if (_curves[i] > time) { + float x = _frames[frame]; + return _curves[i + 1] * (time - x) / (_curves[i] - x); + } + int n = i + DeformTimeline::BEZIER_SIZE; + for (i += 2; i < n; i += 2) { + if (_curves[i] >= time) { + float x = _curves[i - 2], y = _curves[i - 1]; + return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y); + } + } + float x = _curves[n - 2], y = _curves[n - 1]; + return y + (1 - y) * (time - x) / (_frames[frame + getFrameEntries()] - x); +} + +void DeformTimeline::setFrame(int frame, float time, Vector &vertices) { + _frames[frame] = time; + _vertices[frame].clear(); + _vertices[frame].addAll(vertices); +} + +Vector> &DeformTimeline::getVertices() { + return _vertices; +} + +VertexAttachment *DeformTimeline::getAttachment() { + return _attachment; +} + +void DeformTimeline::setAttachment(VertexAttachment *inValue) { + _attachment = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/DrawOrderTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/DrawOrderTimeline.cpp new file mode 100644 index 000000000..26b9dbf90 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/DrawOrderTimeline.cpp @@ -0,0 +1,102 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DrawOrderTimeline, Timeline) + +DrawOrderTimeline::DrawOrderTimeline(size_t frameCount) : Timeline(frameCount, 1) { + PropertyId ids[] = {((PropertyId) Property_DrawOrder << 32)}; + setPropertyIds(ids, 1); + + _drawOrders.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + Vector vec; + _drawOrders.add(vec); + } +} + +void DrawOrderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + Vector &drawOrder = skeleton._drawOrder; + Vector &slots = skeleton._slots; + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } + return; + } + + Vector &drawOrderToSetupIndex = _drawOrders[Animation::search(_frames, time)]; + if (drawOrderToSetupIndex.size() == 0) { + drawOrder.clear(); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } else { + for (size_t i = 0, n = drawOrderToSetupIndex.size(); i < n; ++i) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } +} + +void DrawOrderTimeline::setFrame(size_t frame, float time, Vector &drawOrder) { + _frames[frame] = time; + _drawOrders[frame].clear(); + _drawOrders[frame].addAll(drawOrder); +} + +Vector> &DrawOrderTimeline::getDrawOrders() { + return _drawOrders; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Event.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Event.cpp new file mode 100644 index 000000000..ffe1809ba --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Event.cpp @@ -0,0 +1,90 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +spine::Event::Event(float time, const spine::EventData &data) : _data(data), + _time(time), + _intValue(0), + _floatValue(0), + _stringValue(), + _volume(1), + _balance(0) { +} + +const spine::EventData &spine::Event::getData() { + return _data; +} + +float spine::Event::getTime() { + return _time; +} + +int spine::Event::getIntValue() { + return _intValue; +} + +void spine::Event::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::Event::getFloatValue() { + return _floatValue; +} + +void spine::Event::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::Event::getStringValue() { + return _stringValue; +} + +void spine::Event::setStringValue(const spine::String &inValue) { + _stringValue = inValue; +} + + +float spine::Event::getVolume() { + return _volume; +} + +void spine::Event::setVolume(float inValue) { + _volume = inValue; +} + +float spine::Event::getBalance() { + return _balance; +} + +void spine::Event::setBalance(float inValue) { + _balance = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/EventData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/EventData.cpp new file mode 100644 index 000000000..a6af9dde3 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/EventData.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +spine::EventData::EventData(const spine::String &name) : _name(name), + _intValue(0), + _floatValue(0), + _stringValue(), + _audioPath(), + _volume(1), + _balance(0) { + assert(_name.length() > 0); +} + +/// The name of the event, which is unique within the skeleton. +const spine::String &spine::EventData::getName() const { + return _name; +} + +int spine::EventData::getIntValue() const { + return _intValue; +} + +void spine::EventData::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::EventData::getFloatValue() const { + return _floatValue; +} + +void spine::EventData::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::EventData::getStringValue() const { + return _stringValue; +} + +void spine::EventData::setStringValue(const spine::String &inValue) { + this->_stringValue = inValue; +} + +const spine::String &spine::EventData::getAudioPath() const { + return _audioPath; +} + +void spine::EventData::setAudioPath(const spine::String &inValue) { + _audioPath = inValue; +} + + +float spine::EventData::getVolume() const { + return _volume; +} + +void spine::EventData::setVolume(float inValue) { + _volume = inValue; +} + +float spine::EventData::getBalance() const { + return _balance; +} + +void spine::EventData::setBalance(float inValue) { + _balance = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/EventTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/EventTimeline.cpp new file mode 100644 index 000000000..857b69cbf --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/EventTimeline.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(EventTimeline, Timeline) + +EventTimeline::EventTimeline(size_t frameCount) : Timeline(frameCount, 1) { + PropertyId ids[] = {((PropertyId) Property_Event << 32)}; + setPropertyIds(ids, 1); + _events.setSize(frameCount, NULL); +} + +EventTimeline::~EventTimeline() { + ContainerUtil::cleanUpVectorOfPointers(_events); +} + +void EventTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + if (pEvents == NULL) return; + + Vector &events = *pEvents; + + size_t frameCount = _frames.size(); + + if (lastTime > time) { + // Fire events after last time for looped animations. + apply(skeleton, lastTime, FLT_MAX, pEvents, alpha, blend, direction); + lastTime = -1.0f; + } else if (lastTime >= _frames[frameCount - 1]) { + // Last time is after last i. + return; + } + + if (time < _frames[0]) return;// Time is before first i. + + int i; + if (lastTime < _frames[0]) { + i = 0; + } else { + i = Animation::search(_frames, lastTime) + 1; + float frameTime = _frames[i]; + while (i > 0) { + // Fire multiple events with the same i. + if (_frames[i - 1] != frameTime) break; + i--; + } + } + + for (; (size_t) i < frameCount && time >= _frames[i]; i++) + events.add(_events[i]); +} + +void EventTimeline::setFrame(size_t frame, Event *event) { + _frames[frame] = event->getTime(); + _events[frame] = event; +} + +Vector &EventTimeline::getEvents() { return _events; } diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Extension.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Extension.cpp new file mode 100644 index 000000000..43939bafd --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Extension.cpp @@ -0,0 +1,127 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include + +#include + +using namespace spine; + +SpineExtension *SpineExtension::_instance = NULL; + +void SpineExtension::setInstance(SpineExtension *inValue) { + assert(inValue); + + _instance = inValue; +} + +SpineExtension *SpineExtension::getInstance() { + if (!_instance) _instance = spine::getDefaultExtension(); + assert(_instance); + + return _instance; +} + +SpineExtension::~SpineExtension() { +} + +SpineExtension::SpineExtension() { +} + +DefaultSpineExtension::~DefaultSpineExtension() { +} + +void *DefaultSpineExtension::_alloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + void *ptr = ::malloc(size); + return ptr; +} + +void *DefaultSpineExtension::_calloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + + void *ptr = ::malloc(size); + if (ptr) { + memset(ptr, 0, size); + } + return ptr; +} + +void *DefaultSpineExtension::_realloc(void *ptr, size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + void *mem = NULL; + if (size == 0) + return 0; + if (ptr == NULL) + mem = ::malloc(size); + else + mem = ::realloc(ptr, size); + return mem; +} + +void DefaultSpineExtension::_free(void *mem, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + ::free(mem); +} + +char *DefaultSpineExtension::_readFile(const String &path, int *length) { +#ifndef __EMSCRIPTEN__ + char *data; + FILE *file = fopen(path.buffer(), "rb"); + if (!file) return 0; + + fseek(file, 0, SEEK_END); + *length = (int) ftell(file); + fseek(file, 0, SEEK_SET); + + data = SpineExtension::alloc(*length, __FILE__, __LINE__); + fread(data, 1, *length, file); + fclose(file); + + return data; +#else + return nullptr; +#endif +} + +DefaultSpineExtension::DefaultSpineExtension() : SpineExtension() { +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/IkConstraint.cpp b/Projects/SpineCpp/spine-cpp/src/spine/IkConstraint.cpp new file mode 100644 index 000000000..daf3edcc7 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/IkConstraint.cpp @@ -0,0 +1,379 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(IkConstraint, Updatable) + +void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) { + Bone *p = bone.getParent(); + float pa = p->_a, pb = p->_b, pc = p->_c, pd = p->_d; + float rotationIK = -bone._ashearX - bone._arotation; + float tx = 0, ty = 0; + + switch (bone._inherit) { + case Inherit_OnlyTranslation: + tx = (targetX - bone._worldX) * MathUtil::sign(bone.getSkeleton().getScaleX()); + ty = (targetY - bone._worldY) * MathUtil::sign(bone.getSkeleton().getScaleY()); + break; + case Inherit_NoRotationOrReflection: { + float s = MathUtil::abs(pa * pd - pb * pc) / MathUtil::max(0.0001f, pa * pa + pc * pc); + float sa = pa / bone._skeleton.getScaleX(); + float sc = pc / bone._skeleton.getScaleY(); + pb = -sc * s * bone._skeleton.getScaleX(); + pd = sa * s * bone._skeleton.getScaleY(); + rotationIK += MathUtil::atan2Deg(sc, sa); + } + default: + float x = targetX - p->_worldX, y = targetY - p->_worldY; + float d = pa * pd - pb * pc; + if (MathUtil::abs(d) <= 0.0001f) { + tx = 0; + ty = 0; + } else { + tx = (x * pd - y * pb) / d - bone._ax; + ty = (y * pa - x * pc) / d - bone._ay; + } + } + rotationIK += MathUtil::atan2Deg(ty, tx); + if (bone._ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) rotationIK -= 360; + else if (rotationIK < -180) + rotationIK += 360; + float sx = bone._ascaleX; + float sy = bone._ascaleY; + if (compress || stretch) { + switch (bone._inherit) { + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: + tx = targetX - bone._worldX; + ty = targetY - bone._worldY; + default:; + } + + float b = bone._data.getLength() * sx; + if (b > 0.0001) { + float dd = tx * tx + ty * ty; + if ((compress && dd < b * b) || (stretch && dd > b * b)) { + float s = (MathUtil::sqrt(dd) / b - 1) * alpha + 1; + sx *= s; + if (uniform) sy *= s; + } + } + } + bone.updateWorldTransform(bone._ax, bone._ay, bone._arotation + rotationIK * alpha, sx, sy, bone._ashearX, + bone._ashearY); +} + +void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, + float alpha) { + float a, b, c, d; + float px, py, psx, psy, sx, sy; + float cx, cy, csx, cwx, cwy; + int o1, o2, s2, u; + Bone *pp = parent.getParent(); + float tx, ty, dx, dy, dd, l1, l2, a1, a2, r, td, sd, p; + float id, x, y; + if (parent._inherit != Inherit_Normal || child._inherit != Inherit_Normal) return; + px = parent._ax; + py = parent._ay; + psx = parent._ascaleX; + psy = parent._ascaleY; + sx = psx; + sy = psy; + csx = child._ascaleX; + if (psx < 0) { + psx = -psx; + o1 = 180; + s2 = -1; + } else { + o1 = 0; + s2 = 1; + } + if (psy < 0) { + psy = -psy; + s2 = -s2; + } + if (csx < 0) { + csx = -csx; + o2 = 180; + } else + o2 = 0; + r = psx - psy; + cx = child._ax; + u = (r < 0 ? -r : r) <= 0.0001f; + if (!u || stretch) { + cy = 0; + cwx = parent._a * cx + parent._worldX; + cwy = parent._c * cx + parent._worldY; + } else { + cy = child._ay; + cwx = parent._a * cx + parent._b * cy + parent._worldX; + cwy = parent._c * cx + parent._d * cy + parent._worldY; + } + a = pp->_a; + b = pp->_b; + c = pp->_c; + d = pp->_d; + id = a * d - b * c; + id = MathUtil::abs(id) <= 0.0001f ? 0 : 1 / id; + x = cwx - pp->_worldX; + y = cwy - pp->_worldY; + dx = (x * d - y * b) * id - px; + dy = (y * a - x * c) * id - py; + l1 = MathUtil::sqrt(dx * dx + dy * dy); + l2 = child._data.getLength() * csx; + if (l1 < 0.0001) { + apply(parent, targetX, targetY, false, stretch, false, alpha); + child.updateWorldTransform(cx, cy, 0, child._ascaleX, child._ascaleY, child._ashearX, child._ashearY); + return; + } + x = targetX - pp->_worldX; + y = targetY - pp->_worldY; + tx = (x * d - y * b) * id - px; + ty = (y * a - x * c) * id - py; + dd = tx * tx + ty * ty; + if (softness != 0) { + softness *= psx * (csx + 1) * 0.5f; + td = MathUtil::sqrt(dd); + sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + p = MathUtil::min(1.0f, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) { + float cosine; + l2 *= psx; + cosine = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cosine < -1) { + cosine = -1; + a2 = MathUtil::Pi * bendDir; + } else if (cosine > 1) { + cosine = 1; + a2 = 0; + if (stretch) { + a = (MathUtil::sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + sx *= a; + if (uniform) sy *= a; + } + } else + a2 = MathUtil::acos(cosine) * bendDir; + a = l1 + l2 * cosine; + b = l2 * MathUtil::sin(a2); + a1 = MathUtil::atan2(ty * a - tx * b, tx * a + ty * b); + } else { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ll = l1 * l1, ta = MathUtil::atan2(ty, tx); + float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c0; + if (d >= 0) { + float q = MathUtil::sqrt(d), r0, r1; + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5f; + r0 = q / c2; + r1 = c0 / q; + r = MathUtil::abs(r0) < MathUtil::abs(r1) ? r0 : r1; + if (dd - r * r >= 0) { + y = MathUtil::sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtil::atan2(y, r); + a2 = MathUtil::atan2(y / psy, (r - l1) / psx); + goto break_outer; + } + } + { + float minAngle = MathUtil::Pi, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c0 = -a * l1 / (aa - bb); + if (c0 >= -1 && c0 <= 1) { + c0 = MathUtil::acos(c0); + x = a * MathUtil::cos(c0) + l1; + y = b * MathUtil::sin(c0); + d = x * x + y * y; + if (d < minDist) { + minAngle = c0; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) { + maxAngle = c0; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) * 0.5f) { + a1 = ta - MathUtil::atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } else { + a1 = ta - MathUtil::atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + } +break_outer : { + float os = MathUtil::atan2(cy, cx) * s2; + a1 = (a1 - os) * MathUtil::Rad_Deg + o1 - parent._arotation; + if (a1 > 180) a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.updateWorldTransform(px, py, parent._arotation + a1 * alpha, sx, sy, 0, 0); + a2 = ((a2 + os) * MathUtil::Rad_Deg - child._ashearX) * s2 + o2 - child._arotation; + if (a2 > 180) a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.updateWorldTransform(cx, cy, child._arotation + a2 * alpha, child._ascaleX, child._ascaleY, + child._ashearX, child._ashearY); +} +} + +IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _bendDirection(data.getBendDirection()), + _compress(data.getCompress()), + _stretch(data.getStretch()), + _mix(data.getMix()), + _softness(data.getSoftness()), + _target(skeleton.findBone( + data.getTarget()->getName())), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +void IkConstraint::update(Physics) { + if (_mix == 0) return; + switch (_bones.size()) { + case 1: { + Bone *bone0 = _bones[0]; + apply(*bone0, _target->getWorldX(), _target->getWorldY(), _compress, _stretch, _data._uniform, _mix); + } break; + case 2: { + Bone *bone0 = _bones[0]; + Bone *bone1 = _bones[1]; + apply(*bone0, *bone1, _target->getWorldX(), _target->getWorldY(), _bendDirection, _stretch, _data._uniform, + _softness, + _mix); + } break; + } +} + +int IkConstraint::getOrder() { + return (int) _data.getOrder(); +} + +IkConstraintData &IkConstraint::getData() { + return _data; +} + +Vector &IkConstraint::getBones() { + return _bones; +} + +Bone *IkConstraint::getTarget() { + return _target; +} + +void IkConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +int IkConstraint::getBendDirection() { + return _bendDirection; +} + +void IkConstraint::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraint::getMix() { + return _mix; +} + +void IkConstraint::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraint::getStretch() { + return _stretch; +} + +void IkConstraint::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraint::getCompress() { + return _compress; +} + +void IkConstraint::setCompress(bool inValue) { + _compress = inValue; +} + +bool IkConstraint::isActive() { + return _active; +} + +void IkConstraint::setActive(bool inValue) { + _active = inValue; +} + +float IkConstraint::getSoftness() { + return _softness; +} + +void IkConstraint::setSoftness(float inValue) { + _softness = inValue; +} + +void IkConstraint::setToSetupPose() { + IkConstraintData &data = this->_data; + this->_mix = data._mix; + this->_softness = data._softness; + this->_bendDirection = data._bendDirection; + this->_compress = data._compress; + this->_stretch = data._stretch; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/IkConstraintData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/IkConstraintData.cpp new file mode 100644 index 000000000..00b870a03 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/IkConstraintData.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL(IkConstraintData, ConstraintData) + +IkConstraintData::IkConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _bendDirection(0), + _compress(false), + _stretch(false), + _uniform(false), + _mix(0), + _softness(0) { +} + +Vector &IkConstraintData::getBones() { + return _bones; +} + +BoneData *IkConstraintData::getTarget() { + return _target; +} + +void IkConstraintData::setTarget(BoneData *inValue) { + _target = inValue; +} + +int IkConstraintData::getBendDirection() { + return _bendDirection; +} + +void IkConstraintData::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraintData::getMix() { + return _mix; +} + +void IkConstraintData::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraintData::getStretch() { + return _stretch; +} + +void IkConstraintData::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraintData::getCompress() { + return _compress; +} + +void IkConstraintData::setCompress(bool inValue) { + _compress = inValue; +} + + +bool IkConstraintData::getUniform() { + return _uniform; +} + +void IkConstraintData::setUniform(bool inValue) { + _uniform = inValue; +} + +float IkConstraintData::getSoftness() { + return _softness; +} + +void IkConstraintData::setSoftness(float inValue) { + _softness = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/IkConstraintTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/IkConstraintTimeline.cpp new file mode 100644 index 000000000..210e00eac --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/IkConstraintTimeline.cpp @@ -0,0 +1,141 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(IkConstraintTimeline, CurveTimeline) + +IkConstraintTimeline::IkConstraintTimeline(size_t frameCount, size_t bezierCount, int ikConstraintIndex) + : CurveTimeline(frameCount, IkConstraintTimeline::ENTRIES, bezierCount), _constraintIndex(ikConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_IkConstraint << 32) | ikConstraintIndex}; + setPropertyIds(ids, 1); +} + +void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + IkConstraint *constraintP = skeleton._ikConstraints[_constraintIndex]; + IkConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mix = constraint._data._mix; + constraint._softness = constraint._data._softness; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + case MixBlend_First: + constraint._mix += (constraint._data._mix - constraint._mix) * alpha; + constraint._softness += (constraint._data._softness - constraint._softness) * alpha; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + default: + return; + } + } + + float mix = 0, softness = 0; + int i = Animation::search(_frames, time, IkConstraintTimeline::ENTRIES); + int curveType = (int) _curves[i / IkConstraintTimeline::ENTRIES]; + switch (curveType) { + case IkConstraintTimeline::LINEAR: { + float before = _frames[i]; + mix = _frames[i + IkConstraintTimeline::MIX]; + softness = _frames[i + IkConstraintTimeline::SOFTNESS]; + float t = (time - before) / (_frames[i + IkConstraintTimeline::ENTRIES] - before); + mix += (_frames[i + IkConstraintTimeline::ENTRIES + IkConstraintTimeline::MIX] - mix) * t; + softness += (_frames[i + IkConstraintTimeline::ENTRIES + IkConstraintTimeline::SOFTNESS] - softness) * t; + break; + } + case IkConstraintTimeline::STEPPED: { + mix = _frames[i + IkConstraintTimeline::MIX]; + softness = _frames[i + IkConstraintTimeline::SOFTNESS]; + break; + } + default: { + mix = getBezierValue(time, i, IkConstraintTimeline::MIX, curveType - IkConstraintTimeline::BEZIER); + softness = getBezierValue(time, i, IkConstraintTimeline::SOFTNESS, + curveType + IkConstraintTimeline::BEZIER_SIZE - + IkConstraintTimeline::BEZIER); + } + } + + if (blend == MixBlend_Setup) { + constraint._mix = constraint._data._mix + (mix - constraint._data._mix) * alpha; + constraint._softness = constraint._data._softness + (softness - constraint._data._softness) * alpha; + + if (direction == MixDirection_Out) { + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + } else { + constraint._bendDirection = _frames[i + IkConstraintTimeline::BEND_DIRECTION]; + constraint._compress = _frames[i + IkConstraintTimeline::COMPRESS] != 0; + constraint._stretch = _frames[i + IkConstraintTimeline::STRETCH] != 0; + } + } else { + constraint._mix += (mix - constraint._mix) * alpha; + constraint._softness += (softness - constraint._softness) * alpha; + if (direction == MixDirection_In) { + constraint._bendDirection = _frames[i + IkConstraintTimeline::BEND_DIRECTION]; + constraint._compress = _frames[i + IkConstraintTimeline::COMPRESS] != 0; + constraint._stretch = _frames[i + IkConstraintTimeline::STRETCH] != 0; + } + } +} + +void IkConstraintTimeline::setFrame(int frame, float time, float mix, float softness, int bendDirection, bool compress, + bool stretch) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + MIX] = mix; + _frames[frame + SOFTNESS] = softness; + _frames[frame + BEND_DIRECTION] = (float) bendDirection; + _frames[frame + COMPRESS] = compress ? 1 : 0; + _frames[frame + STRETCH] = stretch ? 1 : 0; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/InheritTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/InheritTimeline.cpp new file mode 100644 index 000000000..f3326d04c --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/InheritTimeline.cpp @@ -0,0 +1,81 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(InheritTimeline, Timeline) + +InheritTimeline::InheritTimeline(size_t frameCount, int boneIndex) : Timeline(frameCount, ENTRIES), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Inherit << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +InheritTimeline::~InheritTimeline() { +} + +void InheritTimeline::setFrame(int frame, float time, Inherit inherit) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + INHERIT] = inherit; +} + + +void InheritTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + SP_UNUSED(alpha); + + Bone *bone = skeleton.getBones()[_boneIndex]; + if (!bone->isActive()) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) bone->setInherit(bone->_data.getInherit()); + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) bone->_inherit = bone->_data.getInherit(); + return; + } + int idx = Animation::search(_frames, time, ENTRIES) + INHERIT; + bone->_inherit = (Inherit) _frames[idx]; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Json.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Json.cpp new file mode 100644 index 000000000..89d1cc9d5 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Json.cpp @@ -0,0 +1,559 @@ +/* +Copyright (c) 2009, Dave Gamble +Copyright (c) 2013, Esoteric Software + +Permission is hereby granted, dispose of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* Json */ +/* JSON parser in CPP, from json.c in the spine-c runtime */ + +#ifndef _DEFAULT_SOURCE +/* Bring strings.h definitions into string.h, where appropriate */ +#define _DEFAULT_SOURCE +#endif + +#ifndef _BSD_SOURCE +/* Bring strings.h definitions into string.h, where appropriate */ +#define _BSD_SOURCE +#endif + +#include +#include +#include + +#include +#include + +using namespace spine; + +const int Json::JSON_FALSE = 0; +const int Json::JSON_TRUE = 1; +const int Json::JSON_NULL = 2; +const int Json::JSON_NUMBER = 3; +const int Json::JSON_STRING = 4; +const int Json::JSON_ARRAY = 5; +const int Json::JSON_OBJECT = 6; + +const char *Json::_error = NULL; + +Json *Json::getItem(Json *object, const char *string) { + Json *c = object->_child; + while (c && json_strcasecmp(c->_name, string)) { + c = c->_next; + } + return c; +} + +Json *Json::getItem(Json *object, int childIndex) { + Json *current = object->_child; + while (current != NULL && childIndex > 0) { + childIndex--; + current = current->_next; + } + return current; +} + +const char *Json::getString(Json *object, const char *name, const char *defaultValue) { + object = getItem(object, name); + if (object) { + return object->_valueString; + } + + return defaultValue; +} + +float Json::getFloat(Json *value, const char *name, float defaultValue) { + value = getItem(value, name); + return value ? value->_valueFloat : defaultValue; +} + +int Json::getInt(Json *value, const char *name, int defaultValue) { + value = getItem(value, name); + return value ? value->_valueInt : defaultValue; +} + +bool Json::getBoolean(spine::Json *value, const char *name, bool defaultValue) { + value = getItem(value, name); + if (value) { + if (value->_valueString) return strcmp(value->_valueString, "true") == 0; + if (value->_type == JSON_NULL) return false; + if (value->_type == JSON_NUMBER) return value->_valueFloat != 0; + if (value->_type == JSON_FALSE) return false; + if (value->_type == JSON_TRUE) return true; + return defaultValue; + } else { + return defaultValue; + } +} + +const char *Json::getError() { + return _error; +} + +Json::Json(const char *value) : _next(NULL), +#if SPINE_JSON_HAVE_PREV + _prev(NULL), +#endif + _child(NULL), + _type(0), + _size(0), + _valueString(NULL), + _valueInt(0), + _valueFloat(0), + _name(NULL) { + if (value) { + value = parseValue(this, skip(value)); + + assert(value); + } +} + +Json::~Json() { + spine::Json *curr = NULL; + spine::Json *next = _child; + do { + curr = next; + if (curr) { + next = curr->_next; + } + delete curr; + } while (next); + + if (_valueString) { + SpineExtension::free(_valueString, __FILE__, __LINE__); + } + + if (_name) { + SpineExtension::free(_name, __FILE__, __LINE__); + } +} + +const char *Json::skip(const char *inValue) { + if (!inValue) { + /* must propagate NULL since it's often called in skip(f(...)) form */ + return NULL; + } + + while (*inValue && (unsigned char) *inValue <= 32) { + inValue++; + } + + return inValue; +} + +const char *Json::parseValue(Json *item, const char *value) { + /* Referenced by constructor, parseArray(), and parseObject(). */ + /* Always called with the result of skip(). */ +#ifdef SPINE_JSON_DEBUG /* Checked at entry to graph, constructor, and after every parse call. */ + if (!value) { + /* Fail on null. */ + return NULL; + } +#endif + + switch (*value) { + case 'n': { + if (!strncmp(value + 1, "ull", 3)) { + item->_type = JSON_NULL; + return value + 4; + } + break; + } + case 'f': { + if (!strncmp(value + 1, "alse", 4)) { + item->_type = JSON_FALSE; + /* calloc prevents us needing item->_type = JSON_FALSE or valueInt = 0 here */ + return value + 5; + } + break; + } + case 't': { + if (!strncmp(value + 1, "rue", 3)) { + item->_type = JSON_TRUE; + item->_valueInt = 1; + return value + 4; + } + break; + } + case '\"': + return parseString(item, value); + case '[': + return parseArray(item, value); + case '{': + return parseObject(item, value); + case '-': /* fallthrough */ + case '0': /* fallthrough */ + case '1': /* fallthrough */ + case '2': /* fallthrough */ + case '3': /* fallthrough */ + case '4': /* fallthrough */ + case '5': /* fallthrough */ + case '6': /* fallthrough */ + case '7': /* fallthrough */ + case '8': /* fallthrough */ + case '9': + return parseNumber(item, value); + default: + break; + } + + _error = value; + return NULL; /* failure. */ +} + +static const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + +const char *Json::parseString(Json *item, const char *str) { + const char *ptr = str + 1; + char *ptr2; + char *out; + int len = 0; + unsigned uc, uc2; + if (*str != '\"') { + /* TODO: don't need this check when called from parseValue, but do need from parseObject */ + _error = str; + return 0; + } /* not a string! */ + + while (*ptr != '\"' && *ptr && ++len) { + if (*ptr++ == '\\') { + ptr++; /* Skip escaped quotes. */ + } + } + + out = SpineExtension::alloc(len + 1, __FILE__, __LINE__); /* The length needed for the string, roughly. */ + if (!out) { + return 0; + } + + ptr = str + 1; + ptr2 = out; + while (*ptr != '\"' && *ptr) { + if (*ptr != '\\') { + *ptr2++ = *ptr++; + } else { + ptr++; + switch (*ptr) { + case 'b': + *ptr2++ = '\b'; + break; + case 'f': + *ptr2++ = '\f'; + break; + case 'n': + *ptr2++ = '\n'; + break; + case 'r': + *ptr2++ = '\r'; + break; + case 't': + *ptr2++ = '\t'; + break; + case 'u': { + /* transcode utf16 to utf8. */ + sscanf(ptr + 1, "%4x", &uc); + ptr += 4; /* get the unicode char. */ + + if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) { + break; /* check for invalid. */ + } + + /* TODO provide an option to ignore surrogates, use unicode replacement character? */ + if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ { + if (ptr[1] != '\\' || ptr[2] != 'u') { + break; /* missing second-half of surrogate. */ + } + sscanf(ptr + 3, "%4x", &uc2); + ptr += 6; + if (uc2 < 0xDC00 || uc2 > 0xDFFF) { + break; /* invalid second-half of surrogate. */ + } + uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); + } + + len = 4; + if (uc < 0x80) { + len = 1; + } else if (uc < 0x800) { + len = 2; + } else if (uc < 0x10000) { + len = 3; + } + ptr2 += len; + + switch (len) { + case 4: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 3: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 2: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 1: + *--ptr2 = (uc | firstByteMark[len]); + } + ptr2 += len; + break; + } + default: + *ptr2++ = *ptr; + break; + } + ptr++; + } + } + + *ptr2 = 0; + + if (*ptr == '\"') { + ptr++; /* TODO error handling if not \" or \0 ? */ + } + + item->_valueString = out; + item->_type = JSON_STRING; + + return ptr; +} + +const char *Json::parseNumber(Json *item, const char *num) { + double result = 0.0; + int negative = 0; + char *ptr = (char *) num; + + if (*ptr == '-') { + negative = -1; + ++ptr; + } + + while (*ptr >= '0' && *ptr <= '9') { + result = result * 10.0 + (*ptr - '0'); + ++ptr; + } + + if (*ptr == '.') { + double fraction = 0.0; + int n = 0; + ++ptr; + + while (*ptr >= '0' && *ptr <= '9') { + fraction = (fraction * 10.0) + (*ptr - '0'); + ++ptr; + ++n; + } + result += fraction / pow(10.0, n); + } + + if (negative) { + result = -result; + } + + if (*ptr == 'e' || *ptr == 'E') { + double exponent = 0; + int expNegative = 0; + ++ptr; + + if (*ptr == '-') { + expNegative = -1; + ++ptr; + } else if (*ptr == '+') { + ++ptr; + } + + while (*ptr >= '0' && *ptr <= '9') { + exponent = (exponent * 10.0) + (*ptr - '0'); + ++ptr; + } + + if (expNegative) { + result = result / pow(10, exponent); + } else { + result = result * pow(10, exponent); + } + } + + if (ptr != num) { + /* Parse success, number found. */ + item->_valueFloat = (float) result; + item->_valueInt = (int) result; + item->_type = JSON_NUMBER; + return ptr; + } else { + /* Parse failure, _error is set. */ + _error = num; + return NULL; + } +} + +const char *Json::parseArray(Json *item, const char *value) { + Json *child; + +#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */ + if (*value != '[') { + ep = value; + return 0; + } /* not an array! */ +#endif + + item->_type = JSON_ARRAY; + value = skip(value + 1); + if (*value == ']') { + return value + 1; /* empty array. */ + } + + item->_child = child = new (__FILE__, __LINE__) Json(NULL); + if (!item->_child) { + return NULL; /* memory fail */ + } + + value = skip(parseValue(child, skip(value))); /* skip any spacing, get the value. */ + + if (!value) { + return NULL; + } + + item->_size = 1; + + while (*value == ',') { + Json *new_item = new (__FILE__, __LINE__) Json(NULL); + if (!new_item) { + return NULL; /* memory fail */ + } + child->_next = new_item; +#if SPINE_JSON_HAVE_PREV + new_item->prev = child; +#endif + child = new_item; + value = skip(parseValue(child, skip(value + 1))); + if (!value) { + return NULL; /* parse fail */ + } + item->_size++; + } + + if (*value == ']') { + return value + 1; /* end of array */ + } + + _error = value; + + return NULL; /* malformed. */ +} + +/* Build an object from the text. */ +const char *Json::parseObject(Json *item, const char *value) { + Json *child; + +#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */ + if (*value != '{') { + ep = value; + return 0; + } /* not an object! */ +#endif + + item->_type = JSON_OBJECT; + value = skip(value + 1); + if (*value == '}') { + return value + 1; /* empty array. */ + } + + item->_child = child = new (__FILE__, __LINE__) Json(NULL); + if (!item->_child) { + return NULL; + } + value = skip(parseString(child, skip(value))); + if (!value) { + return NULL; + } + child->_name = child->_valueString; + child->_valueString = 0; + if (*value != ':') { + _error = value; + return NULL; + } /* fail! */ + + value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */ + if (!value) { + return NULL; + } + + item->_size = 1; + + while (*value == ',') { + Json *new_item = new (__FILE__, __LINE__) Json(NULL); + if (!new_item) { + return NULL; /* memory fail */ + } + child->_next = new_item; +#if SPINE_JSON_HAVE_PREV + new_item->prev = child; +#endif + child = new_item; + value = skip(parseString(child, skip(value + 1))); + if (!value) { + return NULL; + } + child->_name = child->_valueString; + child->_valueString = 0; + if (*value != ':') { + _error = value; + return NULL; + } /* fail! */ + + value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */ + if (!value) { + return NULL; + } + item->_size++; + } + + if (*value == '}') { + return value + 1; /* end of array */ + } + + _error = value; + + return NULL; /* malformed. */ +} + +int Json::json_strcasecmp(const char *s1, const char *s2) { + /* TODO we may be able to elide these NULL checks if we can prove + * the graph and input (only callsite is Json_getItem) should not have NULLs + */ + if (s1 && s2) { +#if defined(_WIN32) + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif + } else { + if (s1 < s2) { + return -1; /* s1 is null, s2 is not */ + } else if (s1 == s2) { + return 0; /* both are null */ + } else { + return 1; /* s2 is nul s1 is not */ + } + } +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/LinkedMesh.cpp b/Projects/SpineCpp/spine-cpp/src/spine/LinkedMesh.cpp new file mode 100644 index 000000000..a0eb7d71f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/LinkedMesh.cpp @@ -0,0 +1,52 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +LinkedMesh::LinkedMesh(MeshAttachment *mesh, const int skinIndex, size_t slotIndex, const String &parent, + bool inheritTimeline) : _mesh(mesh), + _skinIndex(skinIndex), + _skin(""), + _slotIndex(slotIndex), + _parent(parent), + _inheritTimeline(inheritTimeline) { +} + +LinkedMesh::LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, + bool inheritTimeline) : _mesh(mesh), + _skinIndex(-1), + _skin(skin), + _slotIndex(slotIndex), + _parent(parent), + _inheritTimeline(inheritTimeline) { +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Log.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Log.cpp new file mode 100644 index 000000000..70ac7441f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Log.cpp @@ -0,0 +1,123 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +void spine::spDebug_printSkeletonData(SkeletonData *skeletonData) { + int i, n; + spDebug_printBoneDatas(skeletonData->getBones()); + + for (i = 0, n = (int) skeletonData->getAnimations().size(); i < n; i++) { + spDebug_printAnimation(skeletonData->getAnimations()[i]); + } +} + +void _spDebug_printTimelineBase(Timeline *timeline) { + printf(" Timeline %s:\n", timeline->getRTTI().getClassName()); + printf(" frame count: %zu\n", timeline->getFrameCount()); + printf(" frame entries: %zu\n", timeline->getFrameEntries()); + printf(" frames: "); + spDebug_printFloats(timeline->getFrames()); + printf("\n"); +} + +void _spDebug_printCurveTimeline(CurveTimeline *timeline) { + _spDebug_printTimelineBase(timeline); + printf(" curves: "); + spDebug_printFloats(timeline->getCurves()); + printf("\n"); +} + +void spine::spDebug_printTimeline(Timeline *timeline) { + if (timeline->getRTTI().instanceOf(CurveTimeline::rtti)) + _spDebug_printCurveTimeline(static_cast(timeline)); + else + _spDebug_printTimelineBase(timeline); +} + +void spine::spDebug_printAnimation(Animation *animation) { + int i, n; + printf("Animation %s: %zu timelines\n", animation->getName().buffer(), animation->getTimelines().size()); + + for (i = 0, n = (int) animation->getTimelines().size(); i < n; i++) { + Timeline *timeline = animation->getTimelines()[i]; + spDebug_printTimeline(timeline); + } +} + +void spine::spDebug_printBoneDatas(Vector &boneDatas) { + int i, n; + for (i = 0, n = (int) boneDatas.size(); i < n; i++) { + spDebug_printBoneData(boneDatas[i]); + } +} + +void spine::spDebug_printBoneData(BoneData *boneData) { + printf("Bone data %s: %f, %f, %f, %f, %f, %f %f\n", boneData->getName().buffer(), boneData->getRotation(), + boneData->getScaleX(), boneData->getScaleY(), boneData->getX(), boneData->getY(), boneData->getShearX(), + boneData->getShearY()); +} + +void spine::spDebug_printSkeleton(Skeleton *skeleton) { + spDebug_printBones(skeleton->getBones()); +} + +void spine::spDebug_printBones(Vector &bones) { + int i, n; + for (i = 0, n = (int) bones.size(); i < n; i++) { + spDebug_printBone(bones[i]); + } +} + +void spine::spDebug_printBone(Bone *bone) { + printf("Bone %s: %f, %f, %f, %f, %f, %f\n", bone->getData().getName().buffer(), bone->getA(), bone->getB(), + bone->getC(), bone->getD(), bone->getWorldX(), bone->getWorldY()); +} + +void spine::spDebug_printFloats(float *values, int numFloats) { + int i; + printf("(%i) [", numFloats); + for (i = 0; i < numFloats; i++) { + printf("%f, ", values[i]); + } + printf("]"); +} + +void spine::spDebug_printFloats(Vector &values) { + int i, n; + printf("(%zu) [", values.size()); + for (i = 0, n = (int) values.size(); i < n; i++) { + printf("%f, ", values[i]); + } + printf("]"); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/MathUtil.cpp b/Projects/SpineCpp/spine-cpp/src/spine/MathUtil.cpp new file mode 100644 index 000000000..cbfc8b897 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/MathUtil.cpp @@ -0,0 +1,132 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include +#include + +// Required for division by 0 in _isNaN on MSVC +#ifdef _MSC_VER +#pragma warning(disable : 4723) +#endif + +using namespace spine; + +const float MathUtil::Pi = 3.1415926535897932385f; +const float MathUtil::Pi_2 = 3.1415926535897932385f * 2; +const float MathUtil::InvPi_2 = 1 / MathUtil::Pi_2; +const float MathUtil::Deg_Rad = (3.1415926535897932385f / 180.0f); +const float MathUtil::Rad_Deg = (180.0f / 3.1415926535897932385f); + +float MathUtil::abs(float v) { + return ((v) < 0 ? -(v) : (v)); +} + +float MathUtil::sign(float v) { + return ((v) < 0 ? -1.0f : (v) > 0 ? 1.0f + : 0.0f); +} + +float MathUtil::clamp(float x, float min, float max) { + return ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))); +} + +float MathUtil::fmod(float a, float b) { + return (float) ::fmod(a, b); +} + +/// Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323 +/// degrees), largest error of 0.00488 radians (0.2796 degrees). +float MathUtil::atan2(float y, float x) { + return (float) ::atan2(y, x); +} + +float MathUtil::atan2Deg(float y, float x) { + return MathUtil::atan2(y, x) * MathUtil::Rad_Deg; +} + +/// Returns the cosine in radians from a lookup table. +float MathUtil::cos(float radians) { + return (float) ::cos(radians); +} + +/// Returns the sine in radians from a lookup table. +float MathUtil::sin(float radians) { + return (float) ::sin(radians); +} + +float MathUtil::sqrt(float v) { + return (float) ::sqrt(v); +} + +float MathUtil::acos(float v) { + return (float) ::acos(v); +} + +/// Returns the sine in radians from a lookup table. +float MathUtil::sinDeg(float degrees) { + return (float) ::sin(degrees * MathUtil::Deg_Rad); +} + +/// Returns the cosine in radians from a lookup table. +float MathUtil::cosDeg(float degrees) { + return (float) ::cos(degrees * MathUtil::Deg_Rad); +} + +bool MathUtil::isNan(float v) { + return std::isnan(v); +} + +float MathUtil::quietNan() { + return std::nan(""); +} + +float MathUtil::random() { + return ::rand() / (float) RAND_MAX; +} + +float MathUtil::randomTriangular(float min, float max) { + return randomTriangular(min, max, (min + max) * 0.5f); +} + +float MathUtil::randomTriangular(float min, float max, float mode) { + float u = random(); + float d = max - min; + if (u <= (mode - min) / d) return min + sqrt(u * d * (mode - min)); + return max - sqrt((1 - u) * d * (max - mode)); +} + +float MathUtil::pow(float a, float b) { + return (float) ::pow(a, b); +} + +float MathUtil::ceil(float v) { + return ::ceil(v); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/MeshAttachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/MeshAttachment.cpp new file mode 100644 index 000000000..da8cdd28d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/MeshAttachment.cpp @@ -0,0 +1,241 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL(MeshAttachment, VertexAttachment) + +MeshAttachment::MeshAttachment(const String &name) : VertexAttachment(name), + _parentMesh(NULL), + _path(), + _color(1, 1, 1, 1), + _hullLength(0), + _width(0), + _height(0), + _region(NULL), + _sequence(NULL) {} + +MeshAttachment::~MeshAttachment() { + if (_sequence) delete _sequence; +} + +void MeshAttachment::updateRegion() { + if (_uvs.size() != _regionUVs.size()) { + _uvs.setSize(_regionUVs.size(), 0); + } + + if (_region == nullptr) { + return; + } + + int i = 0, n = (int) _regionUVs.size(); + float u = _region->u, v = _region->v; + float width = 0, height = 0; + switch (_region->degrees) { + case 90: { + float textureWidth = _region->height / (_region->u2 - _region->u); + float textureHeight = _region->width / (_region->v2 - _region->v); + u -= (_region->originalHeight - _region->offsetY - _region->height) / textureWidth; + v -= (_region->originalWidth - _region->offsetX - _region->width) / textureHeight; + width = _region->originalHeight / textureWidth; + height = _region->originalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i + 1] * width; + _uvs[i + 1] = v + (1 - _regionUVs[i]) * height; + } + return; + } + case 180: { + float textureWidth = _region->width / (_region->u2 - _region->u); + float textureHeight = _region->height / (_region->v2 - _region->v); + u -= (_region->originalWidth - _region->offsetX - _region->width) / textureWidth; + v -= _region->offsetY / textureHeight; + width = _region->originalWidth / textureWidth; + height = _region->originalHeight / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + (1 - _regionUVs[i]) * width; + _uvs[i + 1] = v + (1 - _regionUVs[i + 1]) * height; + } + return; + } + case 270: { + float textureHeight = _region->height / (_region->v2 - _region->v); + float textureWidth = _region->width / (_region->u2 - _region->u); + u -= _region->offsetY / textureWidth; + v -= _region->offsetX / textureHeight; + width = _region->originalHeight / textureWidth; + height = _region->originalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + (1 - _regionUVs[i + 1]) * width; + _uvs[i + 1] = v + _regionUVs[i] * height; + } + return; + } + default: { + float textureWidth = _region->width / (_region->u2 - _region->u); + float textureHeight = _region->height / (_region->v2 - _region->v); + u -= _region->offsetX / textureWidth; + v -= (_region->originalHeight - _region->offsetY - _region->height) / textureHeight; + width = _region->originalWidth / textureWidth; + height = _region->originalHeight / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i] * width; + _uvs[i + 1] = v + _regionUVs[i + 1] * height; + } + } + } +} + +int MeshAttachment::getHullLength() { + return _hullLength; +} + +void MeshAttachment::setHullLength(int inValue) { + _hullLength = inValue; +} + +Vector &MeshAttachment::getRegionUVs() { + return _regionUVs; +} + +Vector &MeshAttachment::getUVs() { + return _uvs; +} + +Vector &MeshAttachment::getTriangles() { + return _triangles; +} + +const String &MeshAttachment::getPath() { + return _path; +} + +void MeshAttachment::setPath(const String &inValue) { + _path = inValue; +} + +TextureRegion *MeshAttachment::getRegion() { + return _region; +} + +void MeshAttachment::setRegion(TextureRegion *region) { + _region = region; +} + +Sequence *MeshAttachment::getSequence() { + return _sequence; +} + +void MeshAttachment::setSequence(Sequence *sequence) { + _sequence = sequence; +} + +MeshAttachment *MeshAttachment::getParentMesh() { + return _parentMesh; +} + +void MeshAttachment::setParentMesh(MeshAttachment *inValue) { + _parentMesh = inValue; + if (inValue != NULL) { + _bones.clearAndAddAll(inValue->_bones); + _vertices.clearAndAddAll(inValue->_vertices); + _worldVerticesLength = inValue->_worldVerticesLength; + _regionUVs.clearAndAddAll(inValue->_regionUVs); + _triangles.clearAndAddAll(inValue->_triangles); + _hullLength = inValue->_hullLength; + _edges.clearAndAddAll(inValue->_edges); + _width = inValue->_width; + _height = inValue->_height; + } +} + +Vector &MeshAttachment::getEdges() { + return _edges; +} + +float MeshAttachment::getWidth() { + return _width; +} + +void MeshAttachment::setWidth(float inValue) { + _width = inValue; +} + +float MeshAttachment::getHeight() { + return _height; +} + +void MeshAttachment::setHeight(float inValue) { + _height = inValue; +} + +spine::Color &MeshAttachment::getColor() { + return _color; +} + +Attachment *MeshAttachment::copy() { + if (_parentMesh) return newLinkedMesh(); + + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRegion(_region); + copy->setSequence(_sequence != NULL ? _sequence->copy() : NULL); + copy->_path = _path; + copy->_color.set(_color); + + copyTo(copy); + copy->_regionUVs.clearAndAddAll(_regionUVs); + copy->_uvs.clearAndAddAll(_uvs); + copy->_triangles.clearAndAddAll(_triangles); + copy->_hullLength = _hullLength; + + // Nonessential. + copy->_edges.clearAndAddAll(copy->_edges); + copy->_width = _width; + copy->_height = _height; + return copy; +} + +MeshAttachment *MeshAttachment::newLinkedMesh() { + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRegion(_region); + copy->_path = _path; + copy->_color.set(_color); + copy->_timelineAttachment = this->_timelineAttachment; + copy->setParentMesh(_parentMesh ? _parentMesh : this); + if (copy->_region) copy->updateRegion(); + return copy; +} + +void MeshAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride) { + if (_sequence) _sequence->apply(&slot, this); + VertexAttachment::computeWorldVertices(slot, start, count, worldVertices, offset, stride); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PathAttachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PathAttachment.cpp new file mode 100644 index 000000000..35c01779e --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PathAttachment.cpp @@ -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. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL(PathAttachment, VertexAttachment) + +PathAttachment::PathAttachment(const String &name) : VertexAttachment(name), _closed(false), _constantSpeed(false), + _color() { +} + +Vector &PathAttachment::getLengths() { + return _lengths; +} + +bool PathAttachment::isClosed() { + return _closed; +} + +void PathAttachment::setClosed(bool inValue) { + _closed = inValue; +} + +bool PathAttachment::isConstantSpeed() { + return _constantSpeed; +} + +void PathAttachment::setConstantSpeed(bool inValue) { + _constantSpeed = inValue; +} + +Color &PathAttachment::getColor() { + return _color; +} + +Attachment *PathAttachment::copy() { + PathAttachment *copy = new (__FILE__, __LINE__) PathAttachment(getName()); + copyTo(copy); + copy->_lengths.clearAndAddAll(_lengths); + copy->_closed = _closed; + copy->_constantSpeed = _constantSpeed; + return copy; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PathConstraint.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraint.cpp new file mode 100644 index 000000000..9542c3356 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraint.cpp @@ -0,0 +1,588 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraint, Updatable) + +const float PathConstraint::EPSILON = 0.00001f; +const int PathConstraint::NONE = -1; +const int PathConstraint::BEFORE = -2; +const int PathConstraint::AFTER = -3; + +PathConstraint::PathConstraint(PathConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _target(skeleton.findSlot( + data.getTarget()->getName())), + _position(data.getPosition()), + _spacing(data.getSpacing()), + _mixRotate(data.getMixRotate()), + _mixX(data.getMixX()), + _mixY(data.getMixY()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } + + _segments.setSize(10, 0); +} + +void PathConstraint::update(Physics) { + Attachment *baseAttachment = _target->getAttachment(); + if (baseAttachment == NULL || !baseAttachment->getRTTI().instanceOf(PathAttachment::rtti)) { + return; + } + PathAttachment *attachment = static_cast(baseAttachment); + + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY; + if (mixRotate == 0 && mixX == 0 && mixY == 0) return; + + PathConstraintData &data = _data; + bool tangents = data._rotateMode == RotateMode_Tangent, scale = data._rotateMode == RotateMode_ChainScale; + size_t boneCount = _bones.size(); + size_t spacesCount = tangents ? boneCount : boneCount + 1; + _spaces.setSize(spacesCount, 0); + if (scale) _lengths.setSize(boneCount, 0); + float spacing = _spacing; + + switch (data._spacingMode) { + case SpacingMode_Percent: { + if (scale) { + for (size_t i = 0, n = spacesCount - 1; i < n; i++) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + float x = setupLength * bone._a; + float y = setupLength * bone._c; + _lengths[i] = MathUtil::sqrt(x * x + y * y); + } + } + for (size_t i = 1; i < spacesCount; ++i) { + _spaces[i] = spacing; + } + break; + } + case SpacingMode_Proportional: { + float sum = 0; + for (size_t i = 0, n = spacesCount - 1; i < n;) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + if (setupLength < PathConstraint::EPSILON) { + if (scale) _lengths[i] = 0; + _spaces[++i] = spacing; + } else { + float x = setupLength * bone._a, y = setupLength * bone._c; + float length = MathUtil::sqrt(x * x + y * y); + if (scale) _lengths[i] = length; + _spaces[++i] = length; + sum += length; + } + } + if (sum > 0) { + sum = spacesCount / sum * spacing; + for (size_t i = 1; i < spacesCount; i++) { + _spaces[i] *= sum; + } + } + break; + } + default: { + bool lengthSpacing = data._spacingMode == SpacingMode_Length; + for (size_t i = 0, n = spacesCount - 1; i < n;) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + if (setupLength < PathConstraint::EPSILON) { + if (scale) _lengths[i] = 0; + _spaces[++i] = spacing; + } else { + float x = setupLength * bone._a, y = setupLength * bone._c; + float length = MathUtil::sqrt(x * x + y * y); + if (scale) _lengths[i] = length; + _spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + } + } + + Vector &positions = computeWorldPositions(*attachment, (int) spacesCount, tangents); + float boneX = positions[0]; + float boneY = positions[1]; + float offsetRotation = data.getOffsetRotation(); + bool tip; + if (offsetRotation == 0) { + tip = data._rotateMode == RotateMode_Chain; + } else { + tip = false; + Bone &p = _target->getBone(); + offsetRotation *= p.getA() * p.getD() - p.getB() * p.getC() > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + } + + for (size_t i = 0, p = 3; i < boneCount; i++, p += 3) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + bone._worldX += (boneX - bone._worldX) * mixX; + bone._worldY += (boneY - bone._worldY) * mixY; + float x = positions[p]; + float y = positions[p + 1]; + float dx = x - boneX; + float dy = y - boneY; + if (scale) { + float length = _lengths[i]; + if (length >= PathConstraint::EPSILON) { + float s = (MathUtil::sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1; + bone._a *= s; + bone._c *= s; + } + } + + boneX = x; + boneY = y; + + if (mixRotate > 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d, r, cos, sin; + if (tangents) + r = positions[p - 1]; + else if (_spaces[i + 1] < PathConstraint::EPSILON) + r = positions[p + 2]; + else + r = MathUtil::atan2(dy, dx); + + r -= MathUtil::atan2(c, a); + + if (tip) { + cos = MathUtil::cos(r); + sin = MathUtil::sin(r); + float length = bone._data.getLength(); + boneX += (length * (cos * a - sin * c) - dx) * mixRotate; + boneY += (length * (sin * a + cos * c) - dy) * mixRotate; + } else + r += offsetRotation; + + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + cos = MathUtil::cos(r); + sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + } + + bone.updateAppliedTransform(); + } +} + +int PathConstraint::getOrder() { + return (int) _data.getOrder(); +} + +float PathConstraint::getPosition() { + return _position; +} + +void PathConstraint::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraint::getSpacing() { + return _spacing; +} + +void PathConstraint::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraint::getMixRotate() { + return _mixRotate; +} + +void PathConstraint::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float PathConstraint::getMixX() { + return _mixX; +} + +void PathConstraint::setMixX(float inValue) { + _mixX = inValue; +} + +float PathConstraint::getMixY() { + return _mixY; +} + +void PathConstraint::setMixY(float inValue) { + _mixY = inValue; +} + +Vector &PathConstraint::getBones() { + return _bones; +} + +Slot *PathConstraint::getTarget() { + return _target; +} + +void PathConstraint::setTarget(Slot *inValue) { + _target = inValue; +} + +PathConstraintData &PathConstraint::getData() { + return _data; +} + +Vector & +PathConstraint::computeWorldPositions(PathAttachment &path, int spacesCount, bool tangents) { + Slot &target = *_target; + float position = _position; + _positions.setSize(spacesCount * 3 + 2, 0); + Vector &out = _positions; + Vector &world = _world; + bool closed = path.isClosed(); + int verticesLength = (int) path.getWorldVerticesLength(); + int curveCount = verticesLength / 6; + int prevCurve = NONE; + + float pathLength; + if (!path.isConstantSpeed()) { + Vector &lengths = path.getLengths(); + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (_data._positionMode == PositionMode_Percent) position *= pathLength; + + float multiplier = 0; + switch (_data._spacingMode) { + case SpacingMode_Percent: + multiplier = pathLength; + break; + case SpacingMode_Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + } + + world.setSize(8, 0); + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { + float space = _spaces[i] * multiplier; + position += space; + float p = position; + + if (closed) { + p = MathUtil::fmod(p, pathLength); + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + if (prevCurve != BEFORE) { + prevCurve = BEFORE; + path.computeWorldVertices(target, 2, 4, world, 0); + } + + addBeforePosition(p, world, 0, out, o); + + continue; + } else if (p > pathLength) { + if (prevCurve != AFTER) { + prevCurve = AFTER; + path.computeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + + addAfterPosition(p - pathLength, world, 0, out, o); + + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = lengths[curve]; + if (p > length) continue; + + if (curve == 0) + p /= length; + else { + float prev = lengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + if (curve != prevCurve) { + prevCurve = curve; + if (closed && curve == curveCount) { + path.computeWorldVertices(target, verticesLength - 4, 4, world, 0); + path.computeWorldVertices(target, 0, 4, world, 4); + } else + path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0); + } + + addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], + out, o, tangents || (i > 0 && space < EPSILON)); + } + return out; + } + + // World vertices. + if (closed) { + verticesLength += 2; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength - 4, world, 0); + path.computeWorldVertices(target, 0, 2, world, verticesLength - 4); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength, world, 0); + } + + // Curve lengths. + _curves.setSize(curveCount, 0); + pathLength = 0; + float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 0, w = 2; i < curveCount; i++, w += 6) { + cx1 = world[w]; + cy1 = world[w + 1]; + cx2 = world[w + 2]; + cy2 = world[w + 3]; + x2 = world[w + 4]; + y2 = world[w + 5]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + + if (_data._positionMode == PositionMode_Percent) position *= pathLength; + + float multiplier = 0; + switch (_data._spacingMode) { + case SpacingMode_Percent: + multiplier = pathLength; + break; + case SpacingMode_Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + } + + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { + float space = _spaces[i] * multiplier; + position += space; + float p = position; + + if (closed) { + p = MathUtil::fmod(p, pathLength); + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = _curves[curve]; + if (p > length) continue; + if (curve == 0) + p /= length; + else { + float prev = _curves[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + // Curve segment lengths. + if (curve != prevCurve) { + prevCurve = curve; + int ii = curve * 6; + x1 = world[ii]; + y1 = world[ii + 1]; + cx1 = world[ii + 2]; + cy1 = world[ii + 3]; + cx2 = world[ii + 4]; + cy2 = world[ii + 5]; + x2 = world[ii + 6]; + y2 = world[ii + 7]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; + curveLength = MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[9] = curveLength; + segment = 0; + } + + // Weight by segment length. + p *= curveLength; + for (;; segment++) { + float length = _segments[segment]; + if (p > length) continue; + if (segment == 0) + p /= length; + else { + float prev = _segments[segment - 1]; + p = segment + (p - prev) / (length - prev); + } + break; + } + addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, + tangents || (i > 0 && space < EPSILON)); + } + + return out; +} + +void PathConstraint::addBeforePosition(float p, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i]; + float y1 = temp[i + 1]; + float dx = temp[i + 2] - x1; + float dy = temp[i + 3] - y1; + float r = MathUtil::atan2(dy, dx); + output[o] = x1 + p * MathUtil::cos(r); + output[o + 1] = y1 + p * MathUtil::sin(r); + output[o + 2] = r; +} + +void PathConstraint::addAfterPosition(float p, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i + 2]; + float y1 = temp[i + 3]; + float dx = x1 - temp[i]; + float dy = y1 - temp[i + 1]; + float r = MathUtil::atan2(dy, dx); + output[o] = x1 + p * MathUtil::cos(r); + output[o + 1] = y1 + p * MathUtil::sin(r); + output[o + 2] = r; +} + +void PathConstraint::addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, + float y2, Vector &output, int o, bool tangents) { + if (p < EPSILON || MathUtil::isNan(p)) { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1); + return; + } + + float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; + float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; + float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + output[o] = x; + output[o + 1] = y; + if (tangents) { + if (p < 0.001) + output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = MathUtil::atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), + x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } +} + +bool PathConstraint::isActive() { + return _active; +} + +void PathConstraint::setActive(bool inValue) { + _active = inValue; +} + +void PathConstraint::setToSetupPose() { + PathConstraintData &data = this->_data; + this->_position = data._position; + this->_spacing = data._spacing; + this->_mixRotate = data._mixRotate; + this->_mixX = data._mixX; + this->_mixY = data._mixY; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintData.cpp new file mode 100644 index 000000000..d8bd175ee --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintData.cpp @@ -0,0 +1,136 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintData, ConstraintData) + +PathConstraintData::PathConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _positionMode(PositionMode_Fixed), + _spacingMode(SpacingMode_Length), + _rotateMode(RotateMode_Tangent), + _offsetRotation(0), + _position(0), + _spacing(0), + _mixRotate(0), + _mixX(0), + _mixY(0) { +} + +Vector &PathConstraintData::getBones() { + return _bones; +} + +SlotData *PathConstraintData::getTarget() { + return _target; +} + +void PathConstraintData::setTarget(SlotData *inValue) { + _target = inValue; +} + +PositionMode PathConstraintData::getPositionMode() { + return _positionMode; +} + +void PathConstraintData::setPositionMode(PositionMode inValue) { + _positionMode = inValue; +} + +SpacingMode PathConstraintData::getSpacingMode() { + return _spacingMode; +} + +void PathConstraintData::setSpacingMode(SpacingMode inValue) { + _spacingMode = inValue; +} + +RotateMode PathConstraintData::getRotateMode() { + return _rotateMode; +} + +void PathConstraintData::setRotateMode(RotateMode inValue) { + _rotateMode = inValue; +} + +float PathConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +void PathConstraintData::setOffsetRotation(float inValue) { + _offsetRotation = inValue; +} + +float PathConstraintData::getPosition() { + return _position; +} + +void PathConstraintData::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraintData::getSpacing() { + return _spacing; +} + +void PathConstraintData::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraintData::getMixRotate() { + return _mixRotate; +} + +void PathConstraintData::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float PathConstraintData::getMixX() { + return _mixX; +} + +void PathConstraintData::setMixX(float inValue) { + _mixX = inValue; +} + +float PathConstraintData::getMixY() { + return _mixY; +} + +void PathConstraintData::setMixY(float inValue) { + _mixY = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintMixTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintMixTimeline.cpp new file mode 100644 index 000000000..57486434c --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintMixTimeline.cpp @@ -0,0 +1,126 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintMixTimeline, CurveTimeline) + +PathConstraintMixTimeline::PathConstraintMixTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex) + : CurveTimeline(frameCount, PathConstraintMixTimeline::ENTRIES, bezierCount), + _constraintIndex(pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintMix << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +void PathConstraintMixTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraintP = skeleton._pathConstraints[_constraintIndex]; + PathConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mixRotate = constraint._data._mixRotate; + constraint._mixX = constraint._data._mixX; + constraint._mixY = constraint._data._mixY; + return; + case MixBlend_First: + constraint._mixRotate += (constraint._data._mixRotate - constraint._mixRotate) * alpha; + constraint._mixX += (constraint._data._mixX - constraint._mixX) * alpha; + constraint._mixY += (constraint._data._mixY - constraint._mixY) * alpha; + default: { + } + } + return; + } + + float rotate, x, y; + int i = Animation::search(_frames, time, PathConstraintMixTimeline::ENTRIES); + int curveType = (int) _curves[i >> 2]; + switch (curveType) { + case LINEAR: { + float before = _frames[i]; + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + float t = (time - before) / (_frames[i + ENTRIES] - before); + rotate += (_frames[i + ENTRIES + ROTATE] - rotate) * t; + x += (_frames[i + ENTRIES + X] - x) * t; + y += (_frames[i + ENTRIES + Y] - y) * t; + break; + } + case STEPPED: { + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + break; + } + default: { + rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER); + x = getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER); + y = getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER); + } + } + + if (blend == MixBlend_Setup) { + PathConstraintData data = constraint._data; + constraint._mixRotate = data._mixRotate + (rotate - data._mixRotate) * alpha; + constraint._mixX = data._mixX + (x - data._mixX) * alpha; + constraint._mixY = data._mixY + (y - data._mixY) * alpha; + } else { + constraint._mixRotate += (rotate - constraint._mixRotate) * alpha; + constraint._mixX += (x - constraint._mixX) * alpha; + constraint._mixY += (y - constraint._mixY) * alpha; + } +} + +void PathConstraintMixTimeline::setFrame(int frame, float time, float mixRotate, float mixX, float mixY) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + ROTATE] = mixRotate; + _frames[frame + X] = mixX; + _frames[frame + Y] = mixY; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp new file mode 100644 index 000000000..b130b24d2 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp @@ -0,0 +1,66 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintPositionTimeline, CurveTimeline1) + +PathConstraintPositionTimeline::PathConstraintPositionTimeline(size_t frameCount, size_t bezierCount, + int pathConstraintIndex) : CurveTimeline1(frameCount, + bezierCount), + _constraintIndex( + pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintPosition << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +PathConstraintPositionTimeline::~PathConstraintPositionTimeline() { +} + +void PathConstraintPositionTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraint = skeleton._pathConstraints[_constraintIndex]; + if (constraint->_active) constraint->_position = getAbsoluteValue(time, alpha, blend, constraint->_position, constraint->_data._position); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp new file mode 100644 index 000000000..a0d771f65 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp @@ -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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintSpacingTimeline, PathConstraintPositionTimeline) + +PathConstraintSpacingTimeline::PathConstraintSpacingTimeline(size_t frameCount, size_t bezierCount, + int pathConstraintIndex) : CurveTimeline1(frameCount, + bezierCount), + _pathConstraintIndex( + pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintSpacing << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +void PathConstraintSpacingTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraint = skeleton._pathConstraints[_pathConstraintIndex]; + if (constraint->_active) + constraint->_spacing = getAbsoluteValue(time, alpha, blend, constraint->_spacing, constraint->_data._spacing); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraint.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraint.cpp new file mode 100644 index 000000000..66334f6ac --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraint.cpp @@ -0,0 +1,493 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraint, Updatable) + +PhysicsConstraint::PhysicsConstraint(PhysicsConstraintData &data, Skeleton &skeleton) + : _data(data), _skeleton(skeleton) { + _bone = skeleton.getBones()[data.getBone()->getIndex()]; + _inertia = data.getInertia(); + _strength = data.getStrength(); + _damping = data.getDamping(); + _massInverse = data.getMassInverse(); + _wind = data.getWind(); + _gravity = data.getGravity(); + _mix = data.getMix(); + + _reset = true; + _ux = 0; + _uy = 0; + _cx = 0; + _cy = 0; + _tx = 0; + _ty = 0; + _xOffset = 0; + _xVelocity = 0; + _yOffset = 0; + _yVelocity = 0; + _rotateOffset = 0; + _rotateVelocity = 0; + _scaleOffset = 0; + _scaleVelocity = 0; + _active = false; + _remaining = 0; + _lastTime = 0; +} + +PhysicsConstraintData &PhysicsConstraint::getData() { + return _data; +} + +void PhysicsConstraint::setBone(Bone *bone) { + _bone = bone; +} + +Bone *PhysicsConstraint::getBone() { + return _bone; +} + +void PhysicsConstraint::setInertia(float value) { + _inertia = value; +} + +float PhysicsConstraint::getInertia() { + return _inertia; +} + +void PhysicsConstraint::setStrength(float value) { + _strength = value; +} + +float PhysicsConstraint::getStrength() { + return _strength; +} + +void PhysicsConstraint::setDamping(float value) { + _damping = value; +} + +float PhysicsConstraint::getDamping() { + return _damping; +} + +void PhysicsConstraint::setMassInverse(float value) { + _massInverse = value; +} + +float PhysicsConstraint::getMassInverse() { + return _massInverse; +} + +void PhysicsConstraint::setWind(float value) { + _wind = value; +} + +float PhysicsConstraint::getWind() { + return _wind; +} + +void PhysicsConstraint::setGravity(float value) { + _gravity = value; +} + +float PhysicsConstraint::getGravity() { + return _gravity; +} + +void PhysicsConstraint::setMix(float value) { + _mix = value; +} + +float PhysicsConstraint::getMix() { + return _mix; +} + +void PhysicsConstraint::setReset(bool value) { + _reset = value; +} + +bool PhysicsConstraint::getReset() { + return _reset; +} + +void PhysicsConstraint::setUx(float value) { + _ux = value; +} + +float PhysicsConstraint::getUx() { + return _ux; +} + +void PhysicsConstraint::setUy(float value) { + _uy = value; +} + +float PhysicsConstraint::getUy() { + return _uy; +} + +void PhysicsConstraint::setCx(float value) { + _cx = value; +} + +float PhysicsConstraint::getCx() { + return _cx; +} + +void PhysicsConstraint::setCy(float value) { + _cy = value; +} + +float PhysicsConstraint::getCy() { + return _cy; +} + +void PhysicsConstraint::setTx(float value) { + _tx = value; +} + +float PhysicsConstraint::getTx() { + return _tx; +} + +void PhysicsConstraint::setTy(float value) { + _ty = value; +} + +float PhysicsConstraint::getTy() { + return _ty; +} + +void PhysicsConstraint::setXOffset(float value) { + _xOffset = value; +} + +float PhysicsConstraint::getXOffset() { + return _xOffset; +} + +void PhysicsConstraint::setXVelocity(float value) { + _xVelocity = value; +} + +float PhysicsConstraint::getXVelocity() { + return _xVelocity; +} + +void PhysicsConstraint::setYOffset(float value) { + _yOffset = value; +} + +float PhysicsConstraint::getYOffset() { + return _yOffset; +} + +void PhysicsConstraint::setYVelocity(float value) { + _yVelocity = value; +} + +float PhysicsConstraint::getYVelocity() { + return _yVelocity; +} + +void PhysicsConstraint::setRotateOffset(float value) { + _rotateOffset = value; +} + +float PhysicsConstraint::getRotateOffset() { + return _rotateOffset; +} + +void PhysicsConstraint::setRotateVelocity(float value) { + _rotateVelocity = value; +} + +float PhysicsConstraint::getRotateVelocity() { + return _rotateVelocity; +} + +void PhysicsConstraint::setScaleOffset(float value) { + _scaleOffset = value; +} + +float PhysicsConstraint::getScaleOffset() { + return _scaleOffset; +} + +void PhysicsConstraint::setScaleVelocity(float value) { + _scaleVelocity = value; +} + +float PhysicsConstraint::getScaleVelocity() { + return _scaleVelocity; +} + +void PhysicsConstraint::setActive(bool value) { + _active = value; +} + +bool PhysicsConstraint::isActive() { + return _active; +} + +void PhysicsConstraint::setRemaining(float value) { + _remaining = value; +} + +float PhysicsConstraint::getRemaining() { + return _remaining; +} + +void PhysicsConstraint::setLastTime(float value) { + _lastTime = value; +} + +float PhysicsConstraint::getLastTime() { + return _lastTime; +} + +void PhysicsConstraint::reset() { + _remaining = 0; + _lastTime = _skeleton.getTime(); + _reset = true; + _xOffset = 0; + _xVelocity = 0; + _yOffset = 0; + _yVelocity = 0; + _rotateOffset = 0; + _rotateVelocity = 0; + _scaleOffset = 0; + _scaleVelocity = 0; +} + +void PhysicsConstraint::setToSetupPose() { + _inertia = _data.getInertia(); + _strength = _data.getStrength(); + _damping = _data.getDamping(); + _massInverse = _data.getMassInverse(); + _wind = _data.getWind(); + _gravity = _data.getGravity(); + _mix = _data.getMix(); +} +void PhysicsConstraint::update(Physics physics) { + float mix = _mix; + if (mix == 0) return; + + bool x = _data._x > 0; + bool y = _data._y > 0; + bool rotateOrShearX = _data._rotate > 0 || _data._shearX > 0; + bool scaleX = _data._scaleX > 0; + + Bone *bone = _bone; + float l = bone->_data.getLength(); + + switch (physics) { + case Physics::Physics_None: + return; + case Physics::Physics_Reset: + reset(); + // Fall through. + case Physics::Physics_Update: { + float delta = MathUtil::max(_skeleton.getTime() - _lastTime, 0.0f); + _remaining += delta; + _lastTime = _skeleton.getTime(); + + float bx = bone->_worldX, by = bone->_worldY; + if (_reset) { + _reset = false; + _ux = bx; + _uy = by; + } else { + float a = _remaining, i = _inertia, t = _data._step, f = _skeleton.getData()->getReferenceScale(); + float qx = _data._limit * delta, qy = qx * MathUtil::abs(_skeleton.getScaleX()); + qx *= MathUtil::abs(_skeleton.getScaleY()); + if (x || y) { + if (x) { + float u = (_ux - bx) * i; + _xOffset += u > qx ? qx : u < -qx ? -qx + : u; + _ux = bx; + } + if (y) { + float u = (_uy - by) * i; + _yOffset += u > qy ? qy : u < -qy ? -qy + : u; + _uy = by; + } + if (a >= t) { + float d = MathUtil::pow(_damping, 60 * t); + float m = _massInverse * t, e = _strength, w = _wind * f, g = _gravity * f * (Bone::yDown ? -1 : 1); + do { + if (x) { + _xVelocity += (w - _xOffset * e) * m; + _xOffset += _xVelocity * t; + _xVelocity *= d; + } + if (y) { + _yVelocity -= (g + _yOffset * e) * m; + _yOffset += _yVelocity * t; + _yVelocity *= d; + } + a -= t; + } while (a >= t); + } + if (x) bone->_worldX += _xOffset * mix * _data._x; + if (y) bone->_worldY += _yOffset * mix * _data._y; + } + + if (rotateOrShearX || scaleX) { + float ca = MathUtil::atan2(bone->_c, bone->_a), c, s, mr = 0; + float dx = _cx - bone->_worldX, dy = _cy - bone->_worldY; + if (dx > qx) + dx = qx; + else if (dx < -qx)// + dx = -qx; + if (dy > qy) + dy = qy; + else if (dy < -qy)// + dy = -qy; + if (rotateOrShearX) { + mr = (_data._rotate + _data._shearX) * mix; + float r = MathUtil::atan2(dy + _ty, dx + _tx) - ca - _rotateOffset * mr; + _rotateOffset += (r - MathUtil::ceil(r * MathUtil::InvPi_2 - 0.5f) * MathUtil::Pi_2) * i; + r = _rotateOffset * mr + ca; + c = MathUtil::cos(r); + s = MathUtil::sin(r); + if (scaleX) { + r = l * bone->getWorldScaleX(); + if (r > 0) _scaleOffset += (dx * c + dy * s) * i / r; + } + } else { + c = MathUtil::cos(ca); + s = MathUtil::sin(ca); + float r = l * bone->getWorldScaleX(); + if (r > 0) _scaleOffset += (dx * c + dy * s) * i / r; + } + a = _remaining; + if (a >= t) { + float m = _massInverse * t, e = _strength, w = _wind, g = _gravity * (Bone::yDown ? -1 : 1), h = l / f; + float d = MathUtil::pow(_damping, 60 * t); + while (true) { + a -= t; + if (scaleX) { + _scaleVelocity += (w * c - g * s - _scaleOffset * e) * m; + _scaleOffset += _scaleVelocity * t; + _scaleVelocity *= d; + } + if (rotateOrShearX) { + _rotateVelocity -= ((w * s + g * c) * h + _rotateOffset * e) * m; + _rotateOffset += _rotateVelocity * t; + _rotateVelocity *= d; + if (a < t) break; + float r = _rotateOffset * mr + ca; + c = MathUtil::cos(r); + s = MathUtil::sin(r); + } else if (a < t)// + break; + } + } + } + _remaining = a; + } + + _cx = bone->_worldX; + _cy = bone->_worldY; + break; + } + case Physics::Physics_Pose: { + if (x) bone->_worldX += _xOffset * mix * _data._x; + if (y) bone->_worldY += _yOffset * mix * _data._y; + break; + } + } + + if (rotateOrShearX) { + float o = _rotateOffset * mix, s = 0, c = 0, a = 0; + if (_data._shearX > 0) { + float r = 0; + if (_data._rotate > 0) { + r = o * _data._rotate; + s = MathUtil::sin(r); + c = MathUtil::cos(r); + a = bone->_b; + bone->_b = c * a - s * bone->_d; + bone->_d = s * a + c * bone->_d; + } + r += o * _data._shearX; + s = MathUtil::sin(r); + c = MathUtil::cos(r); + a = bone->_a; + bone->_a = c * a - s * bone->_c; + bone->_c = s * a + c * bone->_c; + } else { + o *= _data._rotate; + s = MathUtil::sin(o); + c = MathUtil::cos(o); + a = bone->_a; + bone->_a = c * a - s * bone->_c; + bone->_c = s * a + c * bone->_c; + a = bone->_b; + bone->_b = c * a - s * bone->_d; + bone->_d = s * a + c * bone->_d; + } + } + if (scaleX) { + float s = 1 + _scaleOffset * mix * _data._scaleX; + bone->_a *= s; + bone->_c *= s; + } + if (physics != Physics::Physics_Pose) { + _tx = l * bone->_a; + _ty = l * bone->_c; + } + bone->updateAppliedTransform(); +} + +void PhysicsConstraint::rotate(float x, float y, float degrees) { + float r = degrees * MathUtil::Deg_Rad, cos = MathUtil::cos(r), sin = MathUtil::sin(r); + float dx = _cx - x, dy = _cy - y; + translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy); +} + +void PhysicsConstraint::translate(float x, float y) { + _ux -= x; + _uy -= y; + _cx -= x; + _cy -= y; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintData.cpp new file mode 100644 index 000000000..c0f091b65 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintData.cpp @@ -0,0 +1,223 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraintData, ConstraintData) + +PhysicsConstraintData::PhysicsConstraintData(const String &name) : ConstraintData(name), + _bone(nullptr), + _x(0), _y(0), _rotate(0), _scaleX(0), _shearX(0), _limit(0), + _step(0), _inertia(0), _strength(0), _damping(0), _massInverse(0), _wind(0), _gravity(0), _mix(0), + _inertiaGlobal(false), _strengthGlobal(false), _dampingGlobal(false), _massGlobal(false), + _windGlobal(false), _gravityGlobal(false), _mixGlobal(false) { +} + + +void PhysicsConstraintData::setBone(BoneData *bone) { + _bone = bone; +} + +BoneData *PhysicsConstraintData::getBone() const { + return _bone; +} + +void PhysicsConstraintData::setX(float x) { + _x = x; +} + +float PhysicsConstraintData::getX() const { + return _x; +} + +void PhysicsConstraintData::setY(float y) { + _y = y; +} + +float PhysicsConstraintData::getY() const { + return _y; +} + +void PhysicsConstraintData::setRotate(float rotate) { + _rotate = rotate; +} + +float PhysicsConstraintData::getRotate() const { + return _rotate; +} + +void PhysicsConstraintData::setScaleX(float scaleX) { + _scaleX = scaleX; +} + +float PhysicsConstraintData::getScaleX() const { + return _scaleX; +} + +void PhysicsConstraintData::setShearX(float shearX) { + _shearX = shearX; +} + +float PhysicsConstraintData::getShearX() const { + return _shearX; +} + +void PhysicsConstraintData::setLimit(float limit) { + _limit = limit; +} + +float PhysicsConstraintData::getLimit() const { + return _limit; +} + +void PhysicsConstraintData::setStep(float step) { + _step = step; +} + +float PhysicsConstraintData::getStep() const { + return _step; +} + +void PhysicsConstraintData::setInertia(float inertia) { + _inertia = inertia; +} + +float PhysicsConstraintData::getInertia() const { + return _inertia; +} + +void PhysicsConstraintData::setStrength(float strength) { + _strength = strength; +} + +float PhysicsConstraintData::getStrength() const { + return _strength; +} + +void PhysicsConstraintData::setDamping(float damping) { + _damping = damping; +} + +float PhysicsConstraintData::getDamping() const { + return _damping; +} + +void PhysicsConstraintData::setMassInverse(float massInverse) { + _massInverse = massInverse; +} + +float PhysicsConstraintData::getMassInverse() const { + return _massInverse; +} + +void PhysicsConstraintData::setWind(float wind) { + _wind = wind; +} + +float PhysicsConstraintData::getWind() const { + return _wind; +} + +void PhysicsConstraintData::setGravity(float gravity) { + _gravity = gravity; +} + +float PhysicsConstraintData::getGravity() const { + return _gravity; +} + +void PhysicsConstraintData::setMix(float mix) { + _mix = mix; +} + +float PhysicsConstraintData::getMix() const { + return _mix; +} + +void PhysicsConstraintData::setInertiaGlobal(bool inertiaGlobal) { + _inertiaGlobal = inertiaGlobal; +} + +bool PhysicsConstraintData::isInertiaGlobal() const { + return _inertiaGlobal; +} + +void PhysicsConstraintData::setStrengthGlobal(bool strengthGlobal) { + _strengthGlobal = strengthGlobal; +} + +bool PhysicsConstraintData::isStrengthGlobal() const { + return _strengthGlobal; +} + +void PhysicsConstraintData::setDampingGlobal(bool dampingGlobal) { + _dampingGlobal = dampingGlobal; +} + +bool PhysicsConstraintData::isDampingGlobal() const { + return _dampingGlobal; +} + +void PhysicsConstraintData::setMassGlobal(bool massGlobal) { + _massGlobal = massGlobal; +} + +bool PhysicsConstraintData::isMassGlobal() const { + return _massGlobal; +} + +void PhysicsConstraintData::setWindGlobal(bool windGlobal) { + _windGlobal = windGlobal; +} + +bool PhysicsConstraintData::isWindGlobal() const { + return _windGlobal; +} + +void PhysicsConstraintData::setGravityGlobal(bool gravityGlobal) { + _gravityGlobal = gravityGlobal; +} + +bool PhysicsConstraintData::isGravityGlobal() const { + return _gravityGlobal; +} + +void PhysicsConstraintData::setMixGlobal(bool mixGlobal) { + _mixGlobal = mixGlobal; +} + +bool PhysicsConstraintData::isMixGlobal() const { + return _mixGlobal; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp new file mode 100644 index 000000000..2745e354d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp @@ -0,0 +1,102 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraintTimeline, CurveTimeline) +RTTI_IMPL(PhysicsConstraintInertiaTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintStrengthTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintDampingTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintMassTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintWindTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintGravityTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintMixTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintResetTimeline, Timeline) + +PhysicsConstraintTimeline::PhysicsConstraintTimeline(size_t frameCount, size_t bezierCount, + int constraintIndex, Property property) : CurveTimeline1(frameCount, bezierCount), + _constraintIndex(constraintIndex) { + PropertyId ids[] = {((PropertyId) property << 32) | constraintIndex}; + setPropertyIds(ids, 1); +} + +void PhysicsConstraintTimeline::apply(Skeleton &skeleton, float, float time, Vector *, + float alpha, MixBlend blend, MixDirection) { + if (_constraintIndex == -1) { + float value = time >= _frames[0] ? getCurveValue(time) : 0; + + Vector &physicsConstraints = skeleton.getPhysicsConstraints(); + for (size_t i = 0; i < physicsConstraints.size(); i++) { + PhysicsConstraint *constraint = physicsConstraints[i]; + if (constraint->_active && global(constraint->_data)) + set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint), value)); + } + } else { + PhysicsConstraint *constraint = skeleton.getPhysicsConstraints()[_constraintIndex]; + if (constraint->_active) set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint))); + } +} + +void PhysicsConstraintResetTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *, float alpha, MixBlend blend, MixDirection direction) { + PhysicsConstraint *constraint = nullptr; + if (_constraintIndex != -1) { + constraint = skeleton.getPhysicsConstraints()[_constraintIndex]; + if (!constraint->_active) return; + } + + if (lastTime > time) {// Apply after lastTime for looped animations. + apply(skeleton, lastTime, FLT_MAX, nullptr, alpha, blend, direction); + lastTime = -1; + } else if (lastTime >= _frames[_frames.size() - 1])// Last time is after last frame. + return; + if (time < _frames[0]) return; + + if (lastTime < _frames[0] || time >= _frames[Animation::search(_frames, lastTime) + 1]) { + if (constraint != nullptr) + constraint->reset(); + else { + Vector &physicsConstraints = skeleton.getPhysicsConstraints(); + for (size_t i = 0; i < physicsConstraints.size(); i++) { + if (constraint->_active) constraint->reset(); + } + } + } +} \ No newline at end of file diff --git a/Projects/SpineCpp/spine-cpp/src/spine/PointAttachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/PointAttachment.cpp new file mode 100644 index 000000000..085bc3fd8 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/PointAttachment.cpp @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(PointAttachment, Attachment) + +PointAttachment::PointAttachment(const String &name) : Attachment(name), _x(0), _y(0), _rotation(0), _color() { +} + +void PointAttachment::computeWorldPosition(Bone &bone, float &ox, float &oy) { + bone.localToWorld(_x, _y, ox, oy); +} + +float PointAttachment::computeWorldRotation(Bone &bone) { + float r = _rotation * MathUtil::Deg_Rad, cosine = MathUtil::cos(r), sine = MathUtil::sin(r); + float x = cosine * bone._a + sine * bone._b; + float y = cosine * bone._c + sine * bone._d; + return MathUtil::atan2Deg(y, x); +} + +float PointAttachment::getX() { + return _x; +} + +void PointAttachment::setX(float inValue) { + _x = inValue; +} + +float PointAttachment::getY() { + return _y; +} + +void PointAttachment::setY(float inValue) { + _y = inValue; +} + +float PointAttachment::getRotation() { + return _rotation; +} + +void PointAttachment::setRotation(float inValue) { + _rotation = inValue; +} + +Color &PointAttachment::getColor() { + return _color; +} + +Attachment *PointAttachment::copy() { + PointAttachment *copy = new (__FILE__, __LINE__) PointAttachment(getName()); + copy->_x = _x; + copy->_y = _y; + copy->_rotation = _rotation; + return copy; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/RTTI.cpp b/Projects/SpineCpp/spine-cpp/src/spine/RTTI.cpp new file mode 100644 index 000000000..e8dbb9495 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/RTTI.cpp @@ -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. + *****************************************************************************/ + +#include +#include + +using namespace spine; + +RTTI::RTTI(const char *className) : _className(className), _pBaseRTTI(NULL) { +} + +RTTI::RTTI(const char *className, const RTTI &baseRTTI) : _className(className), _pBaseRTTI(&baseRTTI) { +} + +const char *RTTI::getClassName() const { + return _className; +} + +bool RTTI::isExactly(const RTTI &rtti) const { + return !strcmp(this->_className, rtti._className); +} + +bool RTTI::instanceOf(const RTTI &rtti) const { + const RTTI *pCompare = this; + while (pCompare) { + if (!strcmp(pCompare->_className, rtti._className)) return true; + pCompare = pCompare->_pBaseRTTI; + } + return false; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/RegionAttachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/RegionAttachment.cpp new file mode 100644 index 000000000..f669b656f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/RegionAttachment.cpp @@ -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. + *****************************************************************************/ + +#include + +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(RegionAttachment, Attachment) + +const int RegionAttachment::BLX = 0; +const int RegionAttachment::BLY = 1; +const int RegionAttachment::ULX = 2; +const int RegionAttachment::ULY = 3; +const int RegionAttachment::URX = 4; +const int RegionAttachment::URY = 5; +const int RegionAttachment::BRX = 6; +const int RegionAttachment::BRY = 7; + +RegionAttachment::RegionAttachment(const String &name) : Attachment(name), + _x(0), + _y(0), + _rotation(0), + _scaleX(1), + _scaleY(1), + _width(0), + _height(0), + _path(), + _color(1, 1, 1, 1), + _region(NULL), + _sequence(NULL) { + _vertexOffset.setSize(NUM_UVS, 0); + _uvs.setSize(NUM_UVS, 0); +} + +RegionAttachment::~RegionAttachment() { + if (_sequence) delete _sequence; +} + +void RegionAttachment::updateRegion() { + if (_region == NULL) { + _uvs[BLX] = 0; + _uvs[BLY] = 0; + _uvs[ULX] = 0; + _uvs[ULY] = 1; + _uvs[URX] = 1; + _uvs[URY] = 1; + _uvs[BRX] = 1; + _uvs[BRY] = 0; + return; + } + + float regionScaleX = _width / _region->originalWidth * _scaleX; + float regionScaleY = _height / _region->originalHeight * _scaleY; + float localX = -_width / 2 * _scaleX + _region->offsetX * regionScaleX; + float localY = -_height / 2 * _scaleY + _region->offsetY * regionScaleY; + float localX2 = localX + _region->width * regionScaleX; + float localY2 = localY + _region->height * regionScaleY; + float cos = MathUtil::cosDeg(_rotation); + float sin = MathUtil::sinDeg(_rotation); + float localXCos = localX * cos + _x; + float localXSin = localX * sin; + float localYCos = localY * cos + _y; + float localYSin = localY * sin; + float localX2Cos = localX2 * cos + _x; + float localX2Sin = localX2 * sin; + float localY2Cos = localY2 * cos + _y; + float localY2Sin = localY2 * sin; + + _vertexOffset[BLX] = localXCos - localYSin; + _vertexOffset[BLY] = localYCos + localXSin; + _vertexOffset[ULX] = localXCos - localY2Sin; + _vertexOffset[ULY] = localY2Cos + localXSin; + _vertexOffset[URX] = localX2Cos - localY2Sin; + _vertexOffset[URY] = localY2Cos + localX2Sin; + _vertexOffset[BRX] = localX2Cos - localYSin; + _vertexOffset[BRY] = localYCos + localX2Sin; + + if (_region->degrees == 90) { + _uvs[URX] = _region->u; + _uvs[URY] = _region->v2; + _uvs[BRX] = _region->u; + _uvs[BRY] = _region->v; + _uvs[BLX] = _region->u2; + _uvs[BLY] = _region->v; + _uvs[ULX] = _region->u2; + _uvs[ULY] = _region->v2; + } else { + _uvs[ULX] = _region->u; + _uvs[ULY] = _region->v2; + _uvs[URX] = _region->u; + _uvs[URY] = _region->v; + _uvs[BRX] = _region->u2; + _uvs[BRY] = _region->v; + _uvs[BLX] = _region->u2; + _uvs[BLY] = _region->v2; + } +} + +void RegionAttachment::computeWorldVertices(Slot &slot, Vector &worldVertices, size_t offset, size_t stride) { + assert(worldVertices.size() >= (offset + 8)); + computeWorldVertices(slot, worldVertices.buffer(), offset, stride); +} + +void RegionAttachment::computeWorldVertices(Slot &slot, float *worldVertices, size_t offset, size_t stride) { + if (_sequence) _sequence->apply(&slot, this); + + Bone &bone = slot.getBone(); + float x = bone.getWorldX(), y = bone.getWorldY(); + float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); + float offsetX, offsetY; + + offsetX = _vertexOffset[BRX]; + offsetY = _vertexOffset[BRY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// br + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[BLX]; + offsetY = _vertexOffset[BLY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[ULX]; + offsetY = _vertexOffset[ULY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[URX]; + offsetY = _vertexOffset[URY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; +} + +float RegionAttachment::getX() { + return _x; +} + +void RegionAttachment::setX(float inValue) { + _x = inValue; +} + +float RegionAttachment::getY() { + return _y; +} + +void RegionAttachment::setY(float inValue) { + _y = inValue; +} + +float RegionAttachment::getRotation() { + return _rotation; +} + +void RegionAttachment::setRotation(float inValue) { + _rotation = inValue; +} + +float RegionAttachment::getScaleX() { + return _scaleX; +} + +void RegionAttachment::setScaleX(float inValue) { + _scaleX = inValue; +} + +float RegionAttachment::getScaleY() { + return _scaleY; +} + +void RegionAttachment::setScaleY(float inValue) { + _scaleY = inValue; +} + +float RegionAttachment::getWidth() { + return _width; +} + +void RegionAttachment::setWidth(float inValue) { + _width = inValue; +} + +float RegionAttachment::getHeight() { + return _height; +} + +void RegionAttachment::setHeight(float inValue) { + _height = inValue; +} + +const String &RegionAttachment::getPath() { + return _path; +} + +void RegionAttachment::setPath(const String &inValue) { + _path = inValue; +} + +TextureRegion *RegionAttachment::getRegion() { + return _region; +} + +void RegionAttachment::setRegion(TextureRegion *region) { + _region = region; +} + +Sequence *RegionAttachment::getSequence() { + return _sequence; +} + +void RegionAttachment::setSequence(Sequence *sequence) { + _sequence = sequence; +} + +Vector &RegionAttachment::getOffset() { + return _vertexOffset; +} + +Vector &RegionAttachment::getUVs() { + return _uvs; +} + +spine::Color &RegionAttachment::getColor() { + return _color; +} + +Attachment *RegionAttachment::copy() { + RegionAttachment *copy = new (__FILE__, __LINE__) RegionAttachment(getName()); + copy->_region = _region; + copy->_path = _path; + copy->_x = _x; + copy->_y = _y; + copy->_scaleX = _scaleX; + copy->_scaleY = _scaleY; + copy->_rotation = _rotation; + copy->_width = _width; + copy->_height = _height; + copy->_uvs.clearAndAddAll(_uvs); + copy->_vertexOffset.clearAndAddAll(_vertexOffset); + copy->_color.set(_color); + copy->_sequence = _sequence != NULL ? _sequence->copy() : NULL; + return copy; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/RotateTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/RotateTimeline.cpp new file mode 100644 index 000000000..2a863b65e --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/RotateTimeline.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(RotateTimeline, CurveTimeline1) + +RotateTimeline::RotateTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Rotate << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +void RotateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->isActive()) bone->_rotation = getRelativeValue(time, alpha, blend, bone->_rotation, bone->getData()._rotation); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/ScaleTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/ScaleTimeline.cpp new file mode 100644 index 000000000..959311962 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/ScaleTimeline.cpp @@ -0,0 +1,194 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ScaleTimeline, CurveTimeline2) + +ScaleTimeline::ScaleTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleX << 32) | boneIndex, + ((PropertyId) Property_ScaleY << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +ScaleTimeline::~ScaleTimeline() {} + +void ScaleTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_scaleX = bone->_data._scaleX; + bone->_scaleY = bone->_data._scaleY; + return; + case MixBlend_First: + bone->_scaleX += (bone->_data._scaleX - bone->_scaleX) * alpha; + bone->_scaleY += (bone->_data._scaleY - bone->_scaleY) * alpha; + default: { + } + } + return; + } + + float x, y; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline2::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline2::BEZIER_SIZE - CurveTimeline2::BEZIER); + } + } + x *= bone->_data._scaleX; + y *= bone->_data._scaleY; + + if (alpha == 1) { + if (blend == MixBlend_Add) { + bone->_scaleX += x - bone->_data._scaleX; + bone->_scaleY += y - bone->_data._scaleY; + } else { + bone->_scaleX = x; + bone->_scaleY = y; + } + } else { + float bx, by; + if (direction == MixDirection_Out) { + switch (blend) { + case MixBlend_Setup: + bx = bone->_data._scaleX; + by = bone->_data._scaleY; + bone->_scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone->_scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = bone->_scaleX; + by = bone->_scaleY; + bone->_scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone->_scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_Add: + bone->_scaleX += (x - bone->_data._scaleX) * alpha; + bone->_scaleY += (y - bone->_data._scaleY) * alpha; + } + } else { + switch (blend) { + case MixBlend_Setup: + bx = MathUtil::abs(bone->_data._scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone->_data._scaleY) * MathUtil::sign(y); + bone->_scaleX = bx + (x - bx) * alpha; + bone->_scaleY = by + (y - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = MathUtil::abs(bone->_scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone->_scaleY) * MathUtil::sign(y); + bone->_scaleX = bx + (x - bx) * alpha; + bone->_scaleY = by + (y - by) * alpha; + break; + case MixBlend_Add: + bone->_scaleX += (x - bone->_data._scaleX) * alpha; + bone->_scaleY += (y - bone->_data._scaleY) * alpha; + } + } + } +} + +RTTI_IMPL(ScaleXTimeline, CurveTimeline1) + +ScaleXTimeline::ScaleXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ScaleXTimeline::~ScaleXTimeline() {} + +void ScaleXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_scaleX = getScaleValue(time, alpha, blend, direction, bone->_scaleX, bone->_data._scaleX); +} + +RTTI_IMPL(ScaleYTimeline, CurveTimeline1) + +ScaleYTimeline::ScaleYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleY << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ScaleYTimeline::~ScaleYTimeline() {} + +void ScaleYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_scaleY = getScaleValue(time, alpha, blend, direction, bone->_scaleX, bone->_data._scaleY); +} \ No newline at end of file diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Sequence.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Sequence.cpp new file mode 100644 index 000000000..e1844b3cc --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Sequence.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include + +using namespace spine; + +Sequence::Sequence(int count) : _id(Sequence::getNextID()), + _regions(), + _start(0), + _digits(0), + _setupIndex(0) { + _regions.setSize(count, NULL); +} + +Sequence::~Sequence() { +} + +Sequence *Sequence::copy() { + Sequence *copy = new (__FILE__, __LINE__) Sequence((int) _regions.size()); + for (size_t i = 0; i < _regions.size(); i++) { + copy->_regions[i] = _regions[i]; + } + copy->_start = _start; + copy->_digits = _digits; + copy->_setupIndex = _setupIndex; + return copy; +} + +void Sequence::apply(Slot *slot, Attachment *attachment) { + int index = slot->getSequenceIndex(); + if (index == -1) index = _setupIndex; + if (index >= (int) _regions.size()) index = (int) _regions.size() - 1; + TextureRegion *region = _regions[index]; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = static_cast(attachment); + if (regionAttachment->getRegion() != region) { + regionAttachment->setRegion(region); + regionAttachment->updateRegion(); + } + } + + if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *meshAttachment = static_cast(attachment); + if (meshAttachment->getRegion() != region) { + meshAttachment->setRegion(region); + meshAttachment->updateRegion(); + } + } +} + +String Sequence::getPath(const String &basePath, int index) { + String result(basePath); + String frame; + frame.append(_start + index); + for (int i = _digits - (int) frame.length(); i > 0; i--) + result.append("0"); + result.append(frame); + return result; +} + +int Sequence::getNextID() { + static int _nextID = 0; + return _nextID; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SequenceTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SequenceTimeline.cpp new file mode 100644 index 000000000..38d307b33 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SequenceTimeline.cpp @@ -0,0 +1,124 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(SequenceTimeline, Timeline) + +SequenceTimeline::SequenceTimeline(size_t frameCount, int slotIndex, Attachment *attachment) : Timeline(frameCount, ENTRIES), _slotIndex(slotIndex), _attachment(attachment) { + int sequenceId = 0; + if (attachment->getRTTI().instanceOf(RegionAttachment::rtti)) sequenceId = ((RegionAttachment *) attachment)->getSequence()->getId(); + if (attachment->getRTTI().instanceOf(MeshAttachment::rtti)) sequenceId = ((MeshAttachment *) attachment)->getSequence()->getId(); + PropertyId ids[] = {((PropertyId) Property_Sequence << 32) | ((slotIndex << 16 | sequenceId) & 0xffffffff)}; + setPropertyIds(ids, 1); +} + +SequenceTimeline::~SequenceTimeline() { +} + +void SequenceTimeline::setFrame(int frame, float time, SequenceMode mode, int index, float delay) { + Vector &frames = this->_frames; + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = mode | (index << 4); + frames[frame + DELAY] = delay; +} + +void SequenceTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(alpha); + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton.getSlots()[_slotIndex]; + if (!slot->getBone().isActive()) return; + Attachment *slotAttachment = slot->getAttachment(); + if (slotAttachment != _attachment) { + if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti) || ((VertexAttachment *) slotAttachment)->getTimelineAttachment() != _attachment) return; + } + + Vector &frames = this->_frames; + if (time < frames[0]) {// Time is before first frame. + if (blend == MixBlend_Setup || blend == MixBlend_First) slot->setSequenceIndex(-1); + return; + } + + int i = Animation::search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int) frames[i + MODE]; + float delay = frames[i + DELAY]; + + Sequence *sequence = NULL; + if (_attachment->getRTTI().instanceOf(RegionAttachment::rtti)) sequence = ((RegionAttachment *) _attachment)->getSequence(); + if (_attachment->getRTTI().instanceOf(MeshAttachment::rtti)) sequence = ((MeshAttachment *) _attachment)->getSequence(); + if (!sequence) return; + int index = modeAndIndex >> 4, count = (int) sequence->getRegions().size(); + int mode = modeAndIndex & 0xf; + if (mode != SequenceMode::hold) { + index += (int) (((time - before) / delay + 0.0001)); + switch (mode) { + case SequenceMode::once: + index = MathUtil::min(count - 1, index); + break; + case SequenceMode::loop: + index %= count; + break; + case SequenceMode::pingpong: { + int n = (count << 1) - 2; + index = n == 0 ? 0 : index % n; + if (index >= count) index = n - index; + break; + } + case SequenceMode::onceReverse: + index = MathUtil::max(count - 1 - index, 0); + break; + case SequenceMode::loopReverse: + index = count - 1 - (index % count); + break; + case SequenceMode::pingpongReverse: { + int n = (count << 1) - 2; + index = n == 0 ? 0 : (index + count - 1) % n; + if (index >= count) index = n - index; + } + } + } + slot->setSequenceIndex(index); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/ShearTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/ShearTimeline.cpp new file mode 100644 index 000000000..8434d0ff2 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/ShearTimeline.cpp @@ -0,0 +1,162 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ShearTimeline, CurveTimeline2) + +ShearTimeline::ShearTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex, + ((PropertyId) Property_ShearY << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +ShearTimeline::~ShearTimeline() { +} + +void ShearTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_shearX = bone->_data._shearX; + bone->_shearY = bone->_data._shearY; + return; + case MixBlend_First: + bone->_shearX += (bone->_data._shearX - bone->_shearX) * alpha; + bone->_shearY += (bone->_data._shearY - bone->_shearY) * alpha; + default: { + } + } + return; + } + + float x, y; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline2::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline2::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline2::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline2::BEZIER_SIZE - CurveTimeline2::BEZIER); + } + } + + switch (blend) { + case MixBlend_Setup: + bone->_shearX = bone->_data._shearX + x * alpha; + bone->_shearY = bone->_data._shearY + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone->_shearX += (bone->_data._shearX + x - bone->_shearX) * alpha; + bone->_shearY += (bone->_data._shearY + y - bone->_shearY) * alpha; + break; + case MixBlend_Add: + bone->_shearX += x * alpha; + bone->_shearY += y * alpha; + } +} + +RTTI_IMPL(ShearXTimeline, CurveTimeline1) + +ShearXTimeline::ShearXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ShearXTimeline::~ShearXTimeline() { +} + +void ShearXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_shearX = getRelativeValue(time, alpha, blend, bone->_shearX, bone->_data._shearX); +} + +RTTI_IMPL(ShearYTimeline, CurveTimeline1) + +ShearYTimeline::ShearYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ShearYTimeline::~ShearYTimeline() { +} + +void ShearYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_shearY = getRelativeValue(time, alpha, blend, bone->_shearY, bone->_data._shearY); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Skeleton.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Skeleton.cpp new file mode 100644 index 000000000..baaa7b53d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Skeleton.cpp @@ -0,0 +1,773 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace spine; + +Skeleton::Skeleton(SkeletonData *skeletonData) + : _data(skeletonData), _skin(NULL), _color(1, 1, 1, 1), _scaleX(1), + _scaleY(1), _x(0), _y(0), _time(0) { + _bones.ensureCapacity(_data->getBones().size()); + for (size_t i = 0; i < _data->getBones().size(); ++i) { + BoneData *data = _data->getBones()[i]; + + Bone *bone; + if (data->getParent() == NULL) { + bone = new (__FILE__, __LINE__) Bone(*data, *this, NULL); + } else { + Bone *parent = _bones[data->getParent()->getIndex()]; + bone = new (__FILE__, __LINE__) Bone(*data, *this, parent); + parent->getChildren().add(bone); + } + + _bones.add(bone); + } + + _slots.ensureCapacity(_data->getSlots().size()); + _drawOrder.ensureCapacity(_data->getSlots().size()); + for (size_t i = 0; i < _data->getSlots().size(); ++i) { + SlotData *data = _data->getSlots()[i]; + + Bone *bone = _bones[data->getBoneData().getIndex()]; + Slot *slot = new (__FILE__, __LINE__) Slot(*data, *bone); + + _slots.add(slot); + _drawOrder.add(slot); + } + + _ikConstraints.ensureCapacity(_data->getIkConstraints().size()); + for (size_t i = 0; i < _data->getIkConstraints().size(); ++i) { + IkConstraintData *data = _data->getIkConstraints()[i]; + + IkConstraint *constraint = + new (__FILE__, __LINE__) IkConstraint(*data, *this); + + _ikConstraints.add(constraint); + } + + _transformConstraints.ensureCapacity(_data->getTransformConstraints().size()); + for (size_t i = 0; i < _data->getTransformConstraints().size(); ++i) { + TransformConstraintData *data = _data->getTransformConstraints()[i]; + + TransformConstraint *constraint = + new (__FILE__, __LINE__) TransformConstraint(*data, *this); + + _transformConstraints.add(constraint); + } + + _pathConstraints.ensureCapacity(_data->getPathConstraints().size()); + for (size_t i = 0; i < _data->getPathConstraints().size(); ++i) { + PathConstraintData *data = _data->getPathConstraints()[i]; + + PathConstraint *constraint = + new (__FILE__, __LINE__) PathConstraint(*data, *this); + + _pathConstraints.add(constraint); + } + + _physicsConstraints.ensureCapacity(_data->getPhysicsConstraints().size()); + for (size_t i = 0; i < _data->getPhysicsConstraints().size(); ++i) { + PhysicsConstraintData *data = _data->getPhysicsConstraints()[i]; + + PhysicsConstraint *constraint = + new (__FILE__, __LINE__) PhysicsConstraint(*data, *this); + + _physicsConstraints.add(constraint); + } + + updateCache(); +} + +Skeleton::~Skeleton() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); + ContainerUtil::cleanUpVectorOfPointers(_physicsConstraints); +} + +void Skeleton::updateCache() { + _updateCache.clear(); + + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + Bone *bone = _bones[i]; + bone->_sorted = bone->_data.isSkinRequired(); + bone->_active = !bone->_sorted; + } + + if (_skin) { + Vector &skinBones = _skin->getBones(); + for (size_t i = 0, n = skinBones.size(); i < n; i++) { + Bone *bone = _bones[skinBones[i]->getIndex()]; + do { + bone->_sorted = false; + bone->_active = true; + bone = bone->_parent; + } while (bone); + } + } + + size_t ikCount = _ikConstraints.size(); + size_t transformCount = _transformConstraints.size(); + size_t pathCount = _pathConstraints.size(); + size_t physicsCount = _physicsConstraints.size(); + size_t constraintCount = ikCount + transformCount + pathCount + physicsCount; + + size_t i = 0; +continue_outer: + for (; i < constraintCount; ++i) { + for (size_t ii = 0; ii < ikCount; ++ii) { + IkConstraint *constraint = _ikConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortIkConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < transformCount; ++ii) { + TransformConstraint *constraint = _transformConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortTransformConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < pathCount; ++ii) { + PathConstraint *constraint = _pathConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortPathConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < physicsCount; ++ii) { + PhysicsConstraint *constraint = _physicsConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortPhysicsConstraint(constraint); + i++; + goto continue_outer; + } + } + } + + size_t n = _bones.size(); + for (i = 0; i < n; ++i) { + sortBone(_bones[i]); + } +} + +void Skeleton::printUpdateCache() { + for (size_t i = 0; i < _updateCache.size(); i++) { + Updatable *updatable = _updateCache[i]; + if (updatable->getRTTI().isExactly(Bone::rtti)) { + printf("bone %s\n", ((Bone *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(TransformConstraint::rtti)) { + printf("transform constraint %s\n", + ((TransformConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(IkConstraint::rtti)) { + printf("ik constraint %s\n", + ((IkConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(PathConstraint::rtti)) { + printf("path constraint %s\n", + ((PathConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(PhysicsConstraint::rtti)) { + printf("physics constraint %s\n", + ((PhysicsConstraint *) updatable)->getData().getName().buffer()); + } + } +} + +void Skeleton::updateWorldTransform(Physics physics) { + for (size_t i = 0, n = _bones.size(); i < n; i++) { + Bone *bone = _bones[i]; + bone->_ax = bone->_x; + bone->_ay = bone->_y; + bone->_arotation = bone->_rotation; + bone->_ascaleX = bone->_scaleX; + bone->_ascaleY = bone->_scaleY; + bone->_ashearX = bone->_shearX; + bone->_ashearY = bone->_shearY; + } + + for (size_t i = 0, n = _updateCache.size(); i < n; ++i) { + Updatable *updatable = _updateCache[i]; + updatable->update(physics); + } +} + +void Skeleton::updateWorldTransform(Physics physics, Bone *parent) { + // Apply the parent bone transform to the root bone. The root bone always + // inherits scale, rotation and reflection. + Bone *rootBone = getRootBone(); + float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d; + rootBone->_worldX = pa * _x + pb * _y + parent->_worldX; + rootBone->_worldY = pc * _x + pd * _y + parent->_worldY; + + float rx = (rootBone->_rotation + rootBone->_shearX) * MathUtil::Deg_Rad; + float ry = (rootBone->_rotation + 90 + rootBone->_shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * rootBone->_scaleX; + float lb = MathUtil::cos(ry) * rootBone->_scaleY; + float lc = MathUtil::sin(rx) * rootBone->_scaleX; + float ld = MathUtil::sin(ry) * rootBone->_scaleY; + rootBone->_a = (pa * la + pb * lc) * _scaleX; + rootBone->_b = (pa * lb + pb * ld) * _scaleX; + rootBone->_c = (pc * la + pd * lc) * _scaleY; + rootBone->_d = (pc * lb + pd * ld) * _scaleY; + + // Update everything except root bone. + Bone *rb = getRootBone(); + for (size_t i = 0, n = _updateCache.size(); i < n; i++) { + Updatable *updatable = _updateCache[i]; + if (updatable != rb) + updatable->update(physics); + } +} + +void Skeleton::setToSetupPose() { + setBonesToSetupPose(); + setSlotsToSetupPose(); +} + +void Skeleton::setBonesToSetupPose() { + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + _bones[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + _ikConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + _transformConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + _pathConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) { + _physicsConstraints[i]->setToSetupPose(); + } +} + +void Skeleton::setSlotsToSetupPose() { + _drawOrder.clear(); + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _drawOrder.add(_slots[i]); + } + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _slots[i]->setToSetupPose(); + } +} + +Bone *Skeleton::findBone(const String &boneName) { + return ContainerUtil::findWithDataName(_bones, boneName); +} + +Slot *Skeleton::findSlot(const String &slotName) { + return ContainerUtil::findWithDataName(_slots, slotName); +} + +void Skeleton::setSkin(const String &skinName) { + Skin *foundSkin = skinName.isEmpty() ? NULL : _data->findSkin(skinName); + setSkin(foundSkin); +} + +void Skeleton::setSkin(Skin *newSkin) { + if (_skin == newSkin) + return; + if (newSkin != NULL) { + if (_skin != NULL) { + Skeleton &thisRef = *this; + newSkin->attachAll(thisRef, *_skin); + } else { + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slotP = _slots[i]; + Slot &slot = *slotP; + const String &name = slot._data.getAttachmentName(); + if (name.length() > 0) { + Attachment *attachment = newSkin->getAttachment(i, name); + if (attachment != NULL) { + slot.setAttachment(attachment); + } + } + } + } + } + + _skin = newSkin; + updateCache(); +} + +Attachment *Skeleton::getAttachment(const String &slotName, + const String &attachmentName) { + return getAttachment(_data->findSlot(slotName)->getIndex(), attachmentName); +} + +Attachment *Skeleton::getAttachment(int slotIndex, + const String &attachmentName) { + if (attachmentName.isEmpty()) + return NULL; + + if (_skin != NULL) { + Attachment *attachment = _skin->getAttachment(slotIndex, attachmentName); + if (attachment != NULL) { + return attachment; + } + } + + return _data->getDefaultSkin() != NULL + ? _data->getDefaultSkin()->getAttachment(slotIndex, attachmentName) + : NULL; +} + +void Skeleton::setAttachment(const String &slotName, + const String &attachmentName) { + assert(slotName.length() > 0); + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slot = _slots[i]; + if (slot->_data.getName() == slotName) { + Attachment *attachment = NULL; + if (attachmentName.length() > 0) { + attachment = getAttachment((int) i, attachmentName); + + assert(attachment != NULL); + } + + slot->setAttachment(attachment); + + return; + } + } + + printf("Slot not found: %s", slotName.buffer()); + + assert(false); +} + +IkConstraint *Skeleton::findIkConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + IkConstraint *ikConstraint = _ikConstraints[i]; + if (ikConstraint->_data.getName() == constraintName) { + return ikConstraint; + } + } + return NULL; +} + +TransformConstraint * +Skeleton::findTransformConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + TransformConstraint *transformConstraint = _transformConstraints[i]; + if (transformConstraint->_data.getName() == constraintName) { + return transformConstraint; + } + } + + return NULL; +} + +PathConstraint *Skeleton::findPathConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + PathConstraint *constraint = _pathConstraints[i]; + if (constraint->_data.getName() == constraintName) { + return constraint; + } + } + + return NULL; +} + +PhysicsConstraint * +Skeleton::findPhysicsConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) { + PhysicsConstraint *constraint = _physicsConstraints[i]; + if (constraint->_data.getName() == constraintName) { + return constraint; + } + } + + return NULL; +} + +void Skeleton::getBounds(float &outX, float &outY, float &outWidth, + float &outHeight, Vector &outVertexBuffer) { + getBounds(outX, outY, outWidth, outHeight, outVertexBuffer, NULL); +} + +void Skeleton::getBounds(float &outX, float &outY, float &outWidth, + float &outHeight, Vector &outVertexBuffer, SkeletonClipping *clipper) { + static unsigned short quadIndices[] = {0, 1, 2, 2, 3, 0}; + float minX = FLT_MAX; + float minY = FLT_MAX; + float maxX = -FLT_MAX; + float maxY = -FLT_MAX; + + for (size_t i = 0; i < _drawOrder.size(); ++i) { + Slot *slot = _drawOrder[i]; + if (!slot->_bone._active) + continue; + size_t verticesLength = 0; + Attachment *attachment = slot->getAttachment(); + unsigned short *triangles = NULL; + size_t trianglesLength = 0; + + if (attachment != NULL && + attachment->getRTTI().instanceOf(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = + static_cast(attachment); + + verticesLength = 8; + if (outVertexBuffer.size() < 8) { + outVertexBuffer.setSize(8, 0); + } + regionAttachment->computeWorldVertices(*slot, outVertexBuffer, 0); + triangles = quadIndices; + trianglesLength = 6; + } else if (attachment != NULL && + attachment->getRTTI().instanceOf(MeshAttachment::rtti)) { + MeshAttachment *mesh = static_cast(attachment); + + verticesLength = mesh->getWorldVerticesLength(); + if (outVertexBuffer.size() < verticesLength) { + outVertexBuffer.setSize(verticesLength, 0); + } + + mesh->computeWorldVertices(*slot, 0, verticesLength, + outVertexBuffer.buffer(), 0); + triangles = mesh->getTriangles().buffer(); + trianglesLength = mesh->getTriangles().size(); + } else if (attachment != NULL && + attachment->getRTTI().instanceOf(ClippingAttachment::rtti) && clipper != NULL) { + clipper->clipStart(*slot, static_cast(attachment)); + } + + if (verticesLength > 0) { + float *vertices = outVertexBuffer.buffer(); + if (clipper != NULL && clipper->isClipping()) { + clipper->clipTriangles(outVertexBuffer.buffer(), triangles, trianglesLength); + vertices = clipper->getClippedVertices().buffer(); + verticesLength = clipper->getClippedVertices().size(); + } + for (size_t ii = 0; ii < verticesLength; ii += 2) { + float vx = vertices[ii]; + float vy = vertices[ii + 1]; + + minX = MathUtil::min(minX, vx); + minY = MathUtil::min(minY, vy); + maxX = MathUtil::max(maxX, vx); + maxY = MathUtil::max(maxY, vy); + } + } + if (clipper != NULL) clipper->clipEnd(*slot); + } + if (clipper != NULL) clipper->clipEnd(); + + outX = minX; + outY = minY; + outWidth = maxX - minX; + outHeight = maxY - minY; +} + +Bone *Skeleton::getRootBone() { return _bones.size() == 0 ? NULL : _bones[0]; } + +SkeletonData *Skeleton::getData() { return _data; } + +Vector &Skeleton::getBones() { return _bones; } + +Vector &Skeleton::getUpdateCacheList() { return _updateCache; } + +Vector &Skeleton::getSlots() { return _slots; } + +Vector &Skeleton::getDrawOrder() { return _drawOrder; } + +Vector &Skeleton::getIkConstraints() { return _ikConstraints; } + +Vector &Skeleton::getPathConstraints() { + return _pathConstraints; +} + +Vector &Skeleton::getTransformConstraints() { + return _transformConstraints; +} + +Vector &Skeleton::getPhysicsConstraints() { + return _physicsConstraints; +} + +Skin *Skeleton::getSkin() { return _skin; } + +Color &Skeleton::getColor() { return _color; } + +void Skeleton::setPosition(float x, float y) { + _x = x; + _y = y; +} + +float Skeleton::getX() { return _x; } + +void Skeleton::setX(float inValue) { _x = inValue; } + +float Skeleton::getY() { return _y; } + +void Skeleton::setY(float inValue) { _y = inValue; } + +float Skeleton::getScaleX() { return _scaleX; } + +void Skeleton::setScaleX(float inValue) { _scaleX = inValue; } + +float Skeleton::getScaleY() { return _scaleY * (Bone::isYDown() ? -1 : 1); } + +void Skeleton::setScaleY(float inValue) { _scaleY = inValue; } + +void Skeleton::sortIkConstraint(IkConstraint *constraint) { + constraint->_active = + constraint->_target->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + Bone *target = constraint->getTarget(); + sortBone(target); + + Vector &constrained = constraint->getBones(); + Bone *parent = constrained[0]; + sortBone(parent); + + if (constrained.size() == 1) { + _updateCache.add(constraint); + sortReset(parent->_children); + } else { + Bone *child = constrained[constrained.size() - 1]; + sortBone(child); + + _updateCache.add(constraint); + + sortReset(parent->_children); + child->_sorted = true; + } +} + +void Skeleton::sortPathConstraint(PathConstraint *constraint) { + constraint->_active = + constraint->_target->_bone._active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + Slot *slot = constraint->getTarget(); + int slotIndex = slot->getData().getIndex(); + Bone &slotBone = slot->getBone(); + if (_skin != NULL) + sortPathConstraintAttachment(_skin, slotIndex, slotBone); + if (_data->_defaultSkin != NULL && _data->_defaultSkin != _skin) + sortPathConstraintAttachment(_data->_defaultSkin, slotIndex, slotBone); + for (size_t ii = 0, nn = _data->_skins.size(); ii < nn; ii++) + sortPathConstraintAttachment(_data->_skins[ii], slotIndex, slotBone); + + Attachment *attachment = slot->getAttachment(); + if (attachment != NULL && + attachment->getRTTI().instanceOf(PathAttachment::rtti)) + sortPathConstraintAttachment(attachment, slotBone); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; i++) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; i++) + constrained[i]->_sorted = true; +} + +void Skeleton::sortTransformConstraint(TransformConstraint *constraint) { + constraint->_active = + constraint->_target->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + sortBone(constraint->getTarget()); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + if (constraint->_data.isLocal()) { + for (size_t i = 0; i < boneCount; i++) { + Bone *child = constrained[i]; + sortBone(child->getParent()); + sortBone(child); + } + } else { + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; ++i) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; ++i) + constrained[i]->_sorted = true; +} + +void Skeleton::sortPhysicsConstraint(PhysicsConstraint *constraint) { + Bone *bone = constraint->getBone(); + constraint->_active = + bone->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + sortBone(bone); + _updateCache.add(constraint); + sortReset(bone->getChildren()); + bone->_sorted = true; +} + +void Skeleton::sortPathConstraintAttachment(Skin *skin, size_t slotIndex, + Bone &slotBone) { + Skin::AttachmentMap::Entries attachments = skin->getAttachments(); + + while (attachments.hasNext()) { + Skin::AttachmentMap::Entry entry = attachments.next(); + if (entry._slotIndex == slotIndex) { + Attachment *value = entry._attachment; + sortPathConstraintAttachment(value, slotBone); + } + } +} + +void Skeleton::sortPathConstraintAttachment(Attachment *attachment, + Bone &slotBone) { + if (attachment == NULL || + !attachment->getRTTI().instanceOf(PathAttachment::rtti)) + return; + Vector &pathBones = + static_cast(attachment)->getBones(); + if (pathBones.size() == 0) + sortBone(&slotBone); + else { + for (size_t i = 0, n = pathBones.size(); i < n;) { + size_t nn = pathBones[i++]; + nn += i; + while (i < nn) { + sortBone(_bones[pathBones[i++]]); + } + } + } +} + +void Skeleton::sortBone(Bone *bone) { + if (bone->_sorted) + return; + Bone *parent = bone->_parent; + if (parent != NULL) + sortBone(parent); + bone->_sorted = true; + _updateCache.add(bone); +} + +void Skeleton::sortReset(Vector &bones) { + for (size_t i = 0, n = bones.size(); i < n; ++i) { + Bone *bone = bones[i]; + if (!bone->_active) + continue; + if (bone->_sorted) + sortReset(bone->getChildren()); + bone->_sorted = false; + } +} + +float Skeleton::getTime() { return _time; } + +void Skeleton::setTime(float time) { _time = time; } + +void Skeleton::update(float delta) { _time += delta; } + +void Skeleton::physicsTranslate(float x, float y) { + for (int i = 0; i < (int) _physicsConstraints.size(); i++) { + _physicsConstraints[i]->translate(x, y); + } +} + +void Skeleton::physicsRotate(float x, float y, float degrees) { + for (int i = 0; i < (int) _physicsConstraints.size(); i++) { + _physicsConstraints[i]->rotate(x, y, degrees); + } +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SkeletonBinary.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonBinary.cpp new file mode 100644 index 000000000..1fa0871bc --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonBinary.cpp @@ -0,0 +1,1490 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +SkeletonBinary::SkeletonBinary(Atlas *atlasArray) : _attachmentLoader( + new (__FILE__, __LINE__) AtlasAttachmentLoader(atlasArray)), + _error(), _scale(1), _ownsLoader(true) { +} + +SkeletonBinary::SkeletonBinary(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader( + attachmentLoader), + _error(), + _scale(1), + _ownsLoader(ownsLoader) { + assert(_attachmentLoader != NULL); +} + +SkeletonBinary::~SkeletonBinary() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, const int length) { + bool nonessential; + SkeletonData *skeletonData; + + DataInput *input = new (__FILE__, __LINE__) DataInput(); + input->cursor = binary; + input->end = binary + length; + + _linkedMeshes.clear(); + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + char buffer[16] = {0}; + int lowHash = readInt(input); + int hightHash = readInt(input); + String hashString; + snprintf(buffer, 16, "%x", hightHash); + hashString.append(buffer); + snprintf(buffer, 16, "%x", lowHash); + hashString.append(buffer); + skeletonData->_hash = hashString; + + char *skeletonDataVersion = readString(input); + skeletonData->_version.own(skeletonDataVersion); + + if (!skeletonData->_version.startsWith(SPINE_VERSION_STRING)) { + char errorMsg[255]; + snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->_version.buffer(), SPINE_VERSION_STRING); + setError(errorMsg, ""); + delete input; + delete skeletonData; + return NULL; + } + + skeletonData->_x = readFloat(input); + skeletonData->_y = readFloat(input); + skeletonData->_width = readFloat(input); + skeletonData->_height = readFloat(input); + skeletonData->_referenceScale = readFloat(input) * this->_scale; + + nonessential = readBoolean(input); + + if (nonessential) { + skeletonData->_fps = readFloat(input); + skeletonData->_imagesPath.own(readString(input)); + skeletonData->_audioPath.own(readString(input)); + } + + int numStrings = readVarint(input, true); + for (int i = 0; i < numStrings; i++) + skeletonData->_strings.add(readString(input)); + + /* Bones. */ + int numBones = readVarint(input, true); + skeletonData->_bones.setSize(numBones, 0); + for (int i = 0; i < numBones; ++i) { + const char *name = readString(input); + BoneData *parent = i == 0 ? 0 : skeletonData->_bones[readVarint(input, true)]; + BoneData *data = new (__FILE__, __LINE__) BoneData(i, String(name, true), parent); + data->_rotation = readFloat(input); + data->_x = readFloat(input) * _scale; + data->_y = readFloat(input) * _scale; + data->_scaleX = readFloat(input); + data->_scaleY = readFloat(input); + data->_shearX = readFloat(input); + data->_shearY = readFloat(input); + data->_length = readFloat(input) * _scale; + data->_inherit = static_cast(readVarint(input, true)); + data->_skinRequired = readBoolean(input); + if (nonessential) { + readColor(input, data->getColor()); + data->_icon.own(readString(input)); + data->_visible = readBoolean(input); + } + skeletonData->_bones[i] = data; + } + + /* Slots. */ + int slotsCount = readVarint(input, true); + skeletonData->_slots.setSize(slotsCount, 0); + for (int i = 0; i < slotsCount; ++i) { + String slotName = String(readString(input), true); + BoneData *boneData = skeletonData->_bones[readVarint(input, true)]; + SlotData *slotData = new (__FILE__, __LINE__) SlotData(i, slotName, *boneData); + + readColor(input, slotData->getColor()); + unsigned char a = readByte(input); + unsigned char r = readByte(input); + unsigned char g = readByte(input); + unsigned char b = readByte(input); + if (!(r == 0xff && g == 0xff && b == 0xff && a == 0xff)) { + slotData->getDarkColor().set(r / 255.0f, g / 255.0f, b / 255.0f, 1); + slotData->setHasDarkColor(true); + } + slotData->_attachmentName = readStringRef(input, skeletonData); + slotData->_blendMode = static_cast(readVarint(input, true)); + if (nonessential) { + slotData->_visible = readBoolean(input); + } + skeletonData->_slots[i] = slotData; + } + + /* IK constraints. */ + int ikConstraintsCount = readVarint(input, true); + skeletonData->_ikConstraints.setSize(ikConstraintsCount, 0); + for (int i = 0; i < ikConstraintsCount; ++i) { + const char *name = readString(input); + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + data->_bendDirection = (flags & 2) != 0 ? 1 : -1; + data->_compress = (flags & 4) != 0; + data->_stretch = (flags & 8) != 0; + data->_uniform = (flags & 16) != 0; + if ((flags & 32) != 0) data->_mix = (flags & 64) != 0 ? readFloat(input) : 1; + if ((flags & 128) != 0) data->_softness = readFloat(input) * _scale; + + skeletonData->_ikConstraints[i] = data; + } + + /* Transform constraints. */ + int transformConstraintsCount = readVarint(input, true); + skeletonData->_transformConstraints.setSize(transformConstraintsCount, 0); + for (int i = 0; i < transformConstraintsCount; ++i) { + const char *name = readString(input); + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + data->_local = (flags & 2) != 0; + data->_relative = (flags & 4) != 0; + if ((flags & 8) != 0) data->_offsetRotation = readFloat(input); + if ((flags & 16) != 0) data->_offsetX = readFloat(input) * _scale; + if ((flags & 32) != 0) data->_offsetY = readFloat(input) * _scale; + if ((flags & 64) != 0) data->_offsetScaleX = readFloat(input); + if ((flags & 128) != 0) data->_offsetScaleY = readFloat(input); + flags = readByte(input); + if ((flags & 1) != 0) data->_offsetShearY = readFloat(input); + if ((flags & 2) != 0) data->_mixRotate = readFloat(input); + if ((flags & 4) != 0) data->_mixX = readFloat(input); + if ((flags & 8) != 0) data->_mixY = readFloat(input); + if ((flags & 16) != 0) data->_mixScaleX = readFloat(input); + if ((flags & 32) != 0) data->_mixScaleY = readFloat(input); + if ((flags & 64) != 0) data->_mixShearY = readFloat(input); + + skeletonData->_transformConstraints[i] = data; + } + + /* Path constraints */ + int pathConstraintsCount = readVarint(input, true); + skeletonData->_pathConstraints.setSize(pathConstraintsCount, 0); + for (int i = 0; i < pathConstraintsCount; ++i) { + const char *name = readString(input); + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + data->setSkinRequired(readBoolean(input)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_slots[readVarint(input, true)]; + int flags = readByte(input); + data->_positionMode = (PositionMode) (flags & 1); + data->_spacingMode = (SpacingMode) ((flags >> 1) & 3); + data->_rotateMode = (RotateMode) ((flags >> 3) & 3); + if ((flags & 128) != 0) data->_offsetRotation = readFloat(input); + data->_position = readFloat(input); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = readFloat(input); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) + data->_spacing *= _scale; + data->_mixRotate = readFloat(input); + data->_mixX = readFloat(input); + data->_mixY = readFloat(input); + skeletonData->_pathConstraints[i] = data; + } + + // Physics constraints. + int physicsConstraintsCount = readVarint(input, true); + skeletonData->_physicsConstraints.setSize(physicsConstraintsCount, 0); + for (int i = 0; i < physicsConstraintsCount; i++) { + const char *name = readString(input); + PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData(String(name, true)); + data->_order = readVarint(input, true); + data->_bone = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + if ((flags & 2) != 0) data->_x = readFloat(input); + if ((flags & 4) != 0) data->_y = readFloat(input); + if ((flags & 8) != 0) data->_rotate = readFloat(input); + if ((flags & 16) != 0) data->_scaleX = readFloat(input); + if ((flags & 32) != 0) data->_shearX = readFloat(input); + data->_limit = ((flags & 64) != 0 ? readFloat(input) : 5000) * _scale; + data->_step = 1.f / readByte(input); + data->_inertia = readFloat(input); + data->_strength = readFloat(input); + data->_damping = readFloat(input); + data->_massInverse = (flags & 128) != 0 ? readFloat(input) : 1; + data->_wind = readFloat(input); + data->_gravity = readFloat(input); + flags = readByte(input); + if ((flags & 1) != 0) data->_inertiaGlobal = true; + if ((flags & 2) != 0) data->_strengthGlobal = true; + if ((flags & 4) != 0) data->_dampingGlobal = true; + if ((flags & 8) != 0) data->_massGlobal = true; + if ((flags & 16) != 0) data->_windGlobal = true; + if ((flags & 32) != 0) data->_gravityGlobal = true; + if ((flags & 64) != 0) data->_mixGlobal = true; + data->_mix = (flags & 128) != 0 ? readFloat(input) : 1; + skeletonData->_physicsConstraints[i] = data; + } + + /* Default skin. */ + Skin *defaultSkin = readSkin(input, true, skeletonData, nonessential); + if (defaultSkin) { + skeletonData->_defaultSkin = defaultSkin; + skeletonData->_skins.add(defaultSkin); + } + + if (!this->getError().isEmpty()) { + delete input; + delete skeletonData; + return NULL; + } + + /* Skins. */ + for (size_t i = 0, n = (size_t) readVarint(input, true); i < n; ++i) { + Skin *skin = readSkin(input, false, skeletonData, nonessential); + if (skin) + skeletonData->_skins.add(skin); + else { + delete input; + delete skeletonData; + return NULL; + } + } + + /* Linked meshes. */ + for (int i = 0, n = (int) _linkedMeshes.size(); i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = skeletonData->_skins[linkedMesh->_skinIndex]; + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + delete input; + delete skeletonData; + setError("Parent mesh not found: ", linkedMesh->_parent.buffer()); + return NULL; + } + linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimeline ? static_cast(parent) + : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + if (linkedMesh->_mesh->_region) linkedMesh->_mesh->updateRegion(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + int eventsCount = readVarint(input, true); + skeletonData->_events.setSize(eventsCount, 0); + for (int i = 0; i < eventsCount; ++i) { + const char *name = readString(input); + EventData *eventData = new (__FILE__, __LINE__) EventData(String(name, true)); + eventData->_intValue = readVarint(input, false); + eventData->_floatValue = readFloat(input); + eventData->_stringValue.own(readString(input)); + eventData->_audioPath.own(readString(input)); + if (!eventData->_audioPath.isEmpty()) { + eventData->_volume = readFloat(input); + eventData->_balance = readFloat(input); + } + skeletonData->_events[i] = eventData; + } + + /* Animations. */ + int animationsCount = readVarint(input, true); + skeletonData->_animations.setSize(animationsCount, 0); + for (int i = 0; i < animationsCount; ++i) { + String name(readString(input), true); + Animation *animation = readAnimation(name, input, skeletonData); + if (!animation) { + delete input; + delete skeletonData; + return NULL; + } + skeletonData->_animations[i] = animation; + } + + delete input; + return skeletonData; +} + +SkeletonData *SkeletonBinary::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *binary = SpineExtension::readFile(path.buffer(), &length); + if (length == 0 || !binary) { + setError("Unable to read skeleton file: ", path.buffer()); + return NULL; + } + skeletonData = readSkeletonData((unsigned char *) binary, length); + SpineExtension::free(binary, __FILE__, __LINE__); + return skeletonData; +} + +void SkeletonBinary::setError(const char *value1, const char *value2) { + char message[256]; + int length; + strcpy(message, value1); + length = (int) strlen(value1); + if (value2) strncat(message + length, value2, 255 - length); + _error = String(message); +} + +char *SkeletonBinary::readString(DataInput *input) { + int length = readVarint(input, true); + char *string; + if (length == 0) return NULL; + string = SpineExtension::alloc(length, __FILE__, __LINE__); + memcpy(string, input->cursor, length - 1); + input->cursor += length - 1; + string[length - 1] = '\0'; + return string; +} + +char *SkeletonBinary::readStringRef(DataInput *input, SkeletonData *skeletonData) { + int index = readVarint(input, true); + return index == 0 ? NULL : skeletonData->_strings[index - 1]; +} + +float SkeletonBinary::readFloat(DataInput *input) { + union { + int intValue; + float floatValue; + } intToFloat; + intToFloat.intValue = readInt(input); + return intToFloat.floatValue; +} + +unsigned char SkeletonBinary::readByte(DataInput *input) { + return *input->cursor++; +} + +signed char SkeletonBinary::readSByte(DataInput *input) { + return (signed char) readByte(input); +} + +bool SkeletonBinary::readBoolean(DataInput *input) { + return readByte(input) != 0; +} + +int SkeletonBinary::readInt(DataInput *input) { + int result = readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + return result; +} + +void SkeletonBinary::readColor(DataInput *input, Color &color) { + color.r = readByte(input) / 255.0f; + color.g = readByte(input) / 255.0f; + color.b = readByte(input) / 255.0f; + color.a = readByte(input) / 255.0f; +} + +int SkeletonBinary::readVarint(DataInput *input, bool optimizePositive) { + unsigned char b = readByte(input); + int value = b & 0x7F; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 7; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 14; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 21; + if (b & 0x80) value |= (readByte(input) & 0x7F) << 28; + } + } + } + if (!optimizePositive) value = (((unsigned int) value >> 1) ^ -(value & 1)); + return value; +} + +Skin *SkeletonBinary::readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential) { + Skin *skin; + int slotCount = 0; + if (defaultSkin) { + slotCount = readVarint(input, true); + if (slotCount == 0) return NULL; + skin = new (__FILE__, __LINE__) Skin("default"); + } else { + skin = new (__FILE__, __LINE__) Skin(String(readString(input), true)); + + if (nonessential) readColor(input, skin->getColor()); + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int boneIndex = readVarint(input, true); + if (boneIndex >= (int) skeletonData->_bones.size()) return NULL; + skin->getBones().add(skeletonData->_bones[boneIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int ikIndex = readVarint(input, true); + if (ikIndex >= (int) skeletonData->_ikConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_ikConstraints[ikIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int transformIndex = readVarint(input, true); + if (transformIndex >= (int) skeletonData->_transformConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_transformConstraints[transformIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int pathIndex = readVarint(input, true); + if (pathIndex >= (int) skeletonData->_pathConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_pathConstraints[pathIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int physicsIndex = readVarint(input, true); + if (physicsIndex >= (int) skeletonData->_physicsConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_physicsConstraints[physicsIndex]); + } + slotCount = readVarint(input, true); + } + + for (int i = 0; i < slotCount; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + String name(readStringRef(input, skeletonData)); + Attachment *attachment = readAttachment(input, skin, slotIndex, name, skeletonData, nonessential); + if (attachment) + skin->setAttachment(slotIndex, String(name), attachment); + else { + delete skin; + return NULL; + } + } + } + return skin; +} + +Sequence *SkeletonBinary::readSequence(DataInput *input) { + Sequence *sequence = new (__FILE__, __LINE__) Sequence(readVarint(input, true)); + sequence->_start = readVarint(input, true); + sequence->_digits = readVarint(input, true); + sequence->_setupIndex = readVarint(input, true); + return sequence; +} + +Attachment *SkeletonBinary::readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName, + SkeletonData *skeletonData, bool nonessential) { + + int flags = readByte(input); + String name = (flags & 8) != 0 ? readStringRef(input, skeletonData) : attachmentName; + AttachmentType type = static_cast(flags & 0x7); + switch (type) { + case AttachmentType_Region: { + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + float rotation = (flags & 128) != 0 ? readFloat(input) : 0; + float x = readFloat(input) * _scale; + float y = readFloat(input) * _scale; + float scaleX = readFloat(input); + float scaleY = readFloat(input); + float width = readFloat(input) * _scale; + float height = readFloat(input) * _scale; + RegionAttachment *region = _attachmentLoader->newRegionAttachment(*skin, String(name), String(path), sequence); + if (!region) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + region->_path = path; + region->_rotation = rotation; + region->_x = x; + region->_y = y; + region->_scaleX = scaleX; + region->_scaleY = scaleY; + region->_width = width; + region->_height = height; + region->getColor().set(color); + region->_sequence = sequence; + if (sequence == NULL) region->updateRegion(); + _attachmentLoader->configureAttachment(region); + return region; + } + case AttachmentType_Boundingbox: { + BoundingBoxAttachment *box = _attachmentLoader->newBoundingBoxAttachment(*skin, String(name)); + if (!box) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + readVertices(input, box->getVertices(), box->getBones(), (flags & 16) != 0); + box->setWorldVerticesLength(box->getVertices().size()); + if (nonessential) { + readColor(input, box->getColor()); + } + _attachmentLoader->configureAttachment(box); + return box; + } + case AttachmentType_Mesh: { + Vector uvs; + Vector triangles; + Vector vertices; + Vector bones; + int hullLength; + float width = 0; + float height = 0; + Vector edges; + + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + hullLength = readVarint(input, true); + int verticesLength = readVertices(input, vertices, bones, (flags & 128) != 0); + readFloatArray(input, verticesLength, 1, uvs); + readShortArray(input, triangles, (verticesLength - hullLength - 2) * 3); + + if (nonessential) { + readShortArray(input, edges, readVarint(input, true)); + width = readFloat(input); + height = readFloat(input); + } + + MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path), sequence); + if (!mesh) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + mesh->_path = path; + mesh->_color.set(color); + mesh->_bones.addAll(bones); + mesh->_vertices.addAll(vertices); + mesh->setWorldVerticesLength(verticesLength); + mesh->_triangles.addAll(triangles); + mesh->_regionUVs.addAll(uvs); + if (sequence == NULL) mesh->updateRegion(); + mesh->_hullLength = hullLength; + mesh->_sequence = sequence; + if (nonessential) { + mesh->_edges.addAll(edges); + mesh->_width = width; + mesh->_height = height; + } + _attachmentLoader->configureAttachment(mesh); + return mesh; + } + case AttachmentType_Linkedmesh: { + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + bool inheritTimelines = (flags & 128) != 0; + int skinIndex = readVarint(input, true); + String parent(readStringRef(input, skeletonData)); + float width = 0, height = 0; + if (nonessential) { + width = readFloat(input) * _scale; + height = readFloat(input) * _scale; + } + + MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path), sequence); + if (!mesh) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + mesh->_path = path; + mesh->_color.set(color); + mesh->_sequence = sequence; + if (nonessential) { + mesh->_width = width; + mesh->_height = height; + } + + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, skinIndex, slotIndex, + String(parent), inheritTimelines); + _linkedMeshes.add(linkedMesh); + return mesh; + } + case AttachmentType_Path: { + PathAttachment *path = _attachmentLoader->newPathAttachment(*skin, String(name)); + if (!path) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + path->_closed = (flags & 16) != 0; + path->_constantSpeed = (flags & 32) != 0; + int verticesLength = readVertices(input, path->getVertices(), path->getBones(), (flags & 64) != 0); + path->setWorldVerticesLength(verticesLength); + int lengthsLength = verticesLength / 6; + path->_lengths.setSize(lengthsLength, 0); + for (int i = 0; i < lengthsLength; ++i) { + path->_lengths[i] = readFloat(input) * _scale; + } + if (nonessential) { + readColor(input, path->getColor()); + } + _attachmentLoader->configureAttachment(path); + return path; + } + case AttachmentType_Point: { + PointAttachment *point = _attachmentLoader->newPointAttachment(*skin, String(name)); + if (!point) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + point->_rotation = readFloat(input); + point->_x = readFloat(input) * _scale; + point->_y = readFloat(input) * _scale; + + if (nonessential) { + readColor(input, point->getColor()); + } + _attachmentLoader->configureAttachment(point); + return point; + } + case AttachmentType_Clipping: { + int endSlotIndex = readVarint(input, true); + ClippingAttachment *clip = _attachmentLoader->newClippingAttachment(*skin, name); + if (!clip) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + int verticesLength = readVertices(input, clip->getVertices(), clip->getBones(), (flags & 16) != 0); + clip->setWorldVerticesLength(verticesLength); + clip->_endSlot = skeletonData->_slots[endSlotIndex]; + if (nonessential) { + readColor(input, clip->getColor()); + } + _attachmentLoader->configureAttachment(clip); + return clip; + } + } + return NULL; +} + +int SkeletonBinary::readVertices(DataInput *input, Vector &vertices, Vector &bones, bool weighted) { + float scale = _scale; + int vertexCount = readVarint(input, true); + int verticesLength = vertexCount << 1; + if (!weighted) { + readFloatArray(input, verticesLength, scale, vertices); + return verticesLength; + } + vertices.ensureCapacity(verticesLength * 3 * 3); + bones.ensureCapacity(verticesLength * 3); + for (int i = 0; i < vertexCount; ++i) { + int boneCount = readVarint(input, true); + bones.add(boneCount); + for (int ii = 0; ii < boneCount; ++ii) { + bones.add(readVarint(input, true)); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input)); + } + } + return verticesLength; +} + +void SkeletonBinary::readFloatArray(DataInput *input, int n, float scale, Vector &array) { + array.setSize(n, 0); + + int i; + if (scale == 1) { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input); + } + } else { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input) * scale; + } + } +} + +void SkeletonBinary::readShortArray(DataInput *input, Vector &array, int n) { + array.setSize(n, 0); + for (int i = 0; i < n; ++i) { + array[i] = (short) readVarint(input, true); + } +} + +void SkeletonBinary::setBezier(DataInput *input, CurveTimeline *timeline, int bezier, int frame, int value, float time1, + float time2, + float value1, float value2, float scale) { + float cx1 = readFloat(input); + float cy1 = readFloat(input); + float cx2 = readFloat(input); + float cy2 = readFloat(input); + timeline->setBezier(bezier, frame, value, time1, value1, cx1, cy1 * scale, cx2, cy2 * scale, time2, value2); +} + +void SkeletonBinary::readTimeline(DataInput *input, Vector &timelines, CurveTimeline1 *timeline, float scale) { + float time = readFloat(input); + float value = readFloat(input) * scale; + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = readFloat(input); + float value2 = readFloat(input) * scale; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + } + timelines.add(timeline); +} + +void SkeletonBinary::readTimeline2(DataInput *input, Vector &timelines, CurveTimeline2 *timeline, float scale) { + float time = readFloat(input); + float value1 = readFloat(input) * scale; + float value2 = readFloat(input) * scale; + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nvalue1 = readFloat(input) * scale; + float nvalue2 = readFloat(input) * scale; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale); + setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + timelines.add(timeline); +} + +Animation *SkeletonBinary::readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData) { + Vector timelines; + float scale = _scale; + int numTimelines = readVarint(input, true); + SP_UNUSED(numTimelines); + // Slot timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + switch (timelineType) { + case SLOT_ATTACHMENT: { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frameCount, slotIndex); + for (int frame = 0; frame < frameCount; ++frame) { + float time = readFloat(input); + String attachmentName(readStringRef(input, skeletonData)); + timeline->setFrame(frame, time, attachmentName); + } + timelines.add(timeline); + break; + } + case SLOT_RGBA: { + int bezierCount = readVarint(input, true); + RGBATimeline *timeline = new (__FILE__, __LINE__) RGBATimeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float a = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + + float time2 = readFloat(input); + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + float a2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.add(timeline); + break; + } + case SLOT_RGB: { + int bezierCount = readVarint(input, true); + RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b); + if (frame == frameLast) break; + + float time2 = readFloat(input); + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.add(timeline); + break; + } + case SLOT_RGBA2: { + int bezierCount = readVarint(input, true); + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float a = readByte(input) / 255.0; + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nr = readByte(input) / 255.0; + float ng = readByte(input) / 255.0; + float nb = readByte(input) / 255.0; + float na = readByte(input) / 255.0; + float nr2 = readByte(input) / 255.0; + float ng2 = readByte(input) / 255.0; + float nb2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.add(timeline); + break; + } + case SLOT_RGB2: { + int bezierCount = readVarint(input, true); + RGB2Timeline *timeline = new (__FILE__, __LINE__) RGB2Timeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, r2, g2, b2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nr = readByte(input) / 255.0; + float ng = readByte(input) / 255.0; + float nb = readByte(input) / 255.0; + float nr2 = readByte(input) / 255.0; + float ng2 = readByte(input) / 255.0; + float nb2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.add(timeline); + break; + } + case SLOT_ALPHA: { + int bezierCount = readVarint(input, true); + AlphaTimeline *timeline = new (__FILE__, __LINE__) AlphaTimeline(frameCount, bezierCount, slotIndex); + float time = readFloat(input); + float a = readByte(input) / 255.0; + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, a); + if (frame == frameLast) break; + float time2 = readFloat(input); + float a2 = readByte(input) / 255.0; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + } + time = time2; + a = a2; + } + timelines.add(timeline); + break; + } + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a slot: ", skeletonData->_slots[slotIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int boneIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + if (timelineType == BONE_INHERIT) { + InheritTimeline *timeline = new (__FILE__, __LINE__) InheritTimeline(frameCount, boneIndex); + for (int frame = 0; frame < frameCount; frame++) { + float time = readFloat(input); + Inherit inherit = (Inherit) readByte(input); + timeline->setFrame(frame, time, inherit); + } + timelines.add(timeline); + continue; + } + int bezierCount = readVarint(input, true); + switch (timelineType) { + case BONE_ROTATE: + readTimeline(input, timelines, + new (__FILE__, __LINE__) RotateTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_TRANSLATE: + readTimeline2(input, timelines, new (__FILE__, __LINE__) TranslateTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEX: + readTimeline(input, timelines, new (__FILE__, __LINE__) TranslateXTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEY: + readTimeline(input, timelines, new (__FILE__, __LINE__) TranslateYTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_SCALE: + readTimeline2(input, timelines, + new (__FILE__, __LINE__) ScaleTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SCALEX: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ScaleXTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SCALEY: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ScaleYTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEAR: + readTimeline2(input, timelines, + new (__FILE__, __LINE__) ShearTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEARX: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ShearXTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEARY: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ShearYTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a bone: ", skeletonData->_bones[boneIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // IK timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + int bezierCount = readVarint(input, true); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(frameCount, bezierCount, index); + int flags = readByte(input); + float time = readFloat(input), mix = (flags & 1) != 0 ? ((flags & 2) != 0 ? readFloat(input) : 1) : 0; + float softness = (flags & 4) != 0 ? readFloat(input) * scale : 0; + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mix, softness, (flags & 8) != 0 ? 1 : -1, (flags & 16) != 0, (flags & 32) != 0); + if (frame == frameLast) break; + flags = readByte(input); + float time2 = readFloat(input), mix2 = (flags & 1) != 0 ? ((flags & 2) != 0 ? readFloat(input) : 1) : 0; + float softness2 = (flags & 4) != 0 ? readFloat(input) * scale : 0; + if ((flags & 64) != 0) + timeline->setStepped(frame); + else if ((flags & 128) != 0) { + setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); + } + time = time2; + mix = mix2; + softness = softness2; + } + timelines.add(timeline); + } + + // Transform constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + int bezierCount = readVarint(input, true); + TransformConstraintTimeline *timeline = new TransformConstraintTimeline(frameCount, bezierCount, index); + float time = readFloat(input); + float mixRotate = readFloat(input); + float mixX = readFloat(input); + float mixY = readFloat(input); + float mixScaleX = readFloat(input); + float mixScaleY = readFloat(input); + float mixShearY = readFloat(input); + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame == frameLast) break; + float time2 = readFloat(input); + float mixRotate2 = readFloat(input); + float mixX2 = readFloat(input); + float mixY2 = readFloat(input); + float mixScaleX2 = readFloat(input); + float mixScaleY2 = readFloat(input); + float mixShearY2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixShearY = mixShearY2; + } + timelines.add(timeline); + } + + // Path constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + PathConstraintData *data = skeletonData->_pathConstraints[index]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ii++) { + int type = readByte(input); + int frameCount = readVarint(input, true); + int bezierCount = readVarint(input, true); + switch (type) { + case PATH_POSITION: { + readTimeline(input, timelines, new PathConstraintPositionTimeline(frameCount, bezierCount, index), + data->_positionMode == PositionMode_Fixed ? scale : 1); + break; + } + case PATH_SPACING: { + readTimeline(input, timelines, + new PathConstraintSpacingTimeline(frameCount, + bezierCount, + index), + data->_spacingMode == SpacingMode_Length || + data->_spacingMode == SpacingMode_Fixed + ? scale + : 1); + break; + } + case PATH_MIX: + PathConstraintMixTimeline *timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index); + float time = readFloat(input); + float mixRotate = readFloat(input); + float mixX = readFloat(input); + float mixY = readFloat(input); + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = readFloat(input); + float mixRotate2 = readFloat(input); + float mixX2 = readFloat(input); + float mixY2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.add(timeline); + } + } + } + + // Physics timelines. + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int index = readVarint(input, true) - 1; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ii++) { + int type = readByte(input); + int frameCount = readVarint(input, true); + if (type == PHYSICS_RESET) { + PhysicsConstraintResetTimeline *timeline = new (__FILE__, __LINE__) PhysicsConstraintResetTimeline(frameCount, index); + for (int frame = 0; frame < frameCount; frame++) + timeline->setFrame(frame, readFloat(input)); + timelines.add(timeline); + continue; + } + int bezierCount = readVarint(input, true); + switch (type) { + case PHYSICS_INERTIA: + readTimeline(input, timelines, new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_STRENGTH: + readTimeline(input, timelines, new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_DAMPING: + readTimeline(input, timelines, new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MASS: + readTimeline(input, timelines, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_WIND: + readTimeline(input, timelines, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_GRAVITY: + readTimeline(input, timelines, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MIX: + readTimeline(input, timelines, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1); + } + } + } + + // Attachment timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + Skin *skin = skeletonData->_skins[readVarint(input, true)]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + int slotIndex = readVarint(input, true); + for (int iii = 0, nnn = readVarint(input, true); iii < nnn; iii++) { + const char *attachmentName = readStringRef(input, skeletonData); + Attachment *baseAttachment = skin->getAttachment(slotIndex, String(attachmentName)); + if (!baseAttachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Attachment not found: ", attachmentName); + return NULL; + } + unsigned int timelineType = readByte(input); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + + switch (timelineType) { + case ATTACHMENT_DEFORM: { + VertexAttachment *attachment = static_cast(baseAttachment); + bool weighted = attachment->_bones.size() > 0; + Vector &vertices = attachment->_vertices; + int deformLength = weighted ? (int) vertices.size() / 3 * 2 : (int) vertices.size(); + + int bezierCount = readVarint(input, true); + DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frameCount, bezierCount, slotIndex, + attachment); + + float time = readFloat(input); + for (int frame = 0, bezier = 0;; ++frame) { + Vector deform; + size_t end = (size_t) readVarint(input, true); + if (end == 0) { + if (weighted) { + deform.setSize(deformLength, 0); + for (int iiii = 0; iiii < deformLength; ++iiii) + deform[iiii] = 0; + } else { + deform.clearAndAddAll(vertices); + } + } else { + deform.setSize(deformLength, 0); + size_t start = (size_t) readVarint(input, true); + end += start; + if (scale == 1) { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input); + } else { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input) * scale; + } + + if (!weighted) { + for (size_t v = 0, vn = deform.size(); v < vn; ++v) + deform[v] += vertices[v]; + } + } + + timeline->setFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + } + + timelines.add(timeline); + break; + } + case ATTACHMENT_SEQUENCE: { + SequenceTimeline *timeline = new (__FILE__, __LINE__) SequenceTimeline(frameCount, slotIndex, baseAttachment); + for (int frame = 0; frame < frameCount; frame++) { + float time = readFloat(input); + int modeAndIndex = readInt(input); + float delay = readFloat(input); + timeline->setFrame(frame, time, (spine::SequenceMode)(modeAndIndex & 0xf), modeAndIndex >> 4, delay); + } + timelines.add(timeline); + break; + } + } + } + } + } + + // Draw order timeline. + size_t drawOrderCount = (size_t) readVarint(input, true); + if (drawOrderCount > 0) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrderCount); + + size_t slotCount = skeletonData->_slots.size(); + for (size_t i = 0; i < drawOrderCount; ++i) { + float time = readFloat(input); + size_t offsetCount = (size_t) readVarint(input, true); + + Vector drawOrder; + drawOrder.setSize(slotCount, 0); + for (int ii = (int) slotCount - 1; ii >= 0; --ii) + drawOrder[ii] = -1; + + Vector unchanged; + unchanged.setSize(slotCount - offsetCount, 0); + size_t originalIndex = 0, unchangedIndex = 0; + for (size_t ii = 0; ii < offsetCount; ++ii) { + size_t slotIndex = (size_t) readVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = (int) originalIndex++; + // Set changed items. + size_t index = originalIndex; + drawOrder[index + (size_t) readVarint(input, true)] = (int) originalIndex++; + } + + // Collect remaining unchanged items. + while (originalIndex < slotCount) { + unchanged[unchangedIndex++] = (int) originalIndex++; + } + + // Fill in unchanged items. + for (int ii = (int) slotCount - 1; ii >= 0; --ii) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline->setFrame(i, time, drawOrder); + } + timelines.add(timeline); + } + + // Event timeline. + int eventCount = readVarint(input, true); + if (eventCount > 0) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(eventCount); + + for (int i = 0; i < eventCount; ++i) { + float time = readFloat(input); + EventData *eventData = skeletonData->_events[readVarint(input, true)]; + Event *event = new (__FILE__, __LINE__) Event(time, *eventData); + + event->_intValue = readVarint(input, false); + event->_floatValue = readFloat(input); + const char *event_stringValue = readString(input); + if (event_stringValue == nullptr) { + event->_stringValue = eventData->_stringValue; + } else { + event->_stringValue = String(event_stringValue); + SpineExtension::free(event_stringValue, __FILE__, __LINE__); + } + + if (!eventData->_audioPath.isEmpty()) { + event->_volume = readFloat(input); + event->_balance = readFloat(input); + } + timeline->setFrame(i, event); + } + timelines.add(timeline); + } + + float duration = 0; + for (int i = 0, n = (int) timelines.size(); i < n; i++) { + duration = MathUtil::max(duration, (timelines[i])->getDuration()); + } + return new (__FILE__, __LINE__) Animation(String(name), timelines, duration); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SkeletonBounds.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonBounds.cpp new file mode 100644 index 000000000..9f1e00cd8 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonBounds.cpp @@ -0,0 +1,231 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include + +#include + +using namespace spine; + +SkeletonBounds::SkeletonBounds() : _minX(0), _minY(0), _maxX(0), _maxY(0) { +} + +SkeletonBounds::~SkeletonBounds() { + for (size_t i = 0, n = _polygons.size(); i < n; i++) + _polygonPool.free(_polygons[i]); + _polygons.clear(); +} + +void SkeletonBounds::update(Skeleton &skeleton, bool updateAabb) { + Vector &slots = skeleton.getSlots(); + size_t slotCount = slots.size(); + + _boundingBoxes.clear(); + for (size_t i = 0, n = _polygons.size(); i < n; ++i) { + _polygonPool.free(_polygons[i]); + } + + _polygons.clear(); + + for (size_t i = 0; i < slotCount; i++) { + Slot *slot = slots[i]; + if (!slot->getBone().isActive()) continue; + + Attachment *attachment = slot->getAttachment(); + if (attachment == NULL || !attachment->getRTTI().instanceOf(BoundingBoxAttachment::rtti)) continue; + BoundingBoxAttachment *boundingBox = static_cast(attachment); + _boundingBoxes.add(boundingBox); + + spine::Polygon *polygonP = _polygonPool.obtain(); + _polygons.add(polygonP); + + Polygon &polygon = *polygonP; + + size_t count = boundingBox->getWorldVerticesLength(); + polygon._count = (int) count; + if (polygon._vertices.size() < count) { + polygon._vertices.setSize(count, 0); + } + boundingBox->computeWorldVertices(*slot, polygon._vertices); + } + + if (updateAabb) + aabbCompute(); + else { + _minX = FLT_MIN; + _minY = FLT_MIN; + _maxX = FLT_MAX; + _maxY = FLT_MAX; + } +} + +bool SkeletonBounds::aabbcontainsPoint(float x, float y) { + return x >= _minX && x <= _maxX && y >= _minY && y <= _maxY; +} + +bool SkeletonBounds::aabbintersectsSegment(float x1, float y1, float x2, float y2) { + float minX = _minX; + float minY = _minY; + float maxX = _maxX; + float maxY = _maxY; + + if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || + (y1 >= maxY && y2 >= maxY)) { + return false; + } + + float m = (y2 - y1) / (x2 - x1); + float y = m * (minX - x1) + y1; + if (y > minY && y < maxY) return true; + y = m * (maxX - x1) + y1; + if (y > minY && y < maxY) return true; + float x = (minY - y1) / m + x1; + if (x > minX && x < maxX) return true; + x = (maxY - y1) / m + x1; + if (x > minX && x < maxX) return true; + return false; +} + +bool SkeletonBounds::aabbIntersectsSkeleton(SkeletonBounds bounds) { + return _minX < bounds._maxX && _maxX > bounds._minX && _minY < bounds._maxY && _maxY > bounds._minY; +} + +bool SkeletonBounds::containsPoint(spine::Polygon *polygon, float x, float y) { + Vector &vertices = polygon->_vertices; + int nn = polygon->_count; + + int prevIndex = nn - 2; + bool inside = false; + for (int ii = 0; ii < nn; ii += 2) { + float vertexY = vertices[ii + 1]; + float prevY = vertices[prevIndex + 1]; + if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { + float vertexX = vertices[ii]; + if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) { + inside = !inside; + } + } + prevIndex = ii; + } + return inside; +} + +BoundingBoxAttachment *SkeletonBounds::containsPoint(float x, float y) { + for (size_t i = 0, n = _polygons.size(); i < n; ++i) + if (containsPoint(_polygons[i], x, y)) return _boundingBoxes[i]; + return NULL; +} + +BoundingBoxAttachment *SkeletonBounds::intersectsSegment(float x1, float y1, float x2, float y2) { + for (size_t i = 0, n = _polygons.size(); i < n; ++i) + if (intersectsSegment(_polygons[i], x1, y1, x2, y2)) return _boundingBoxes[i]; + return NULL; +} + +bool SkeletonBounds::intersectsSegment(spine::Polygon *polygon, float x1, float y1, float x2, float y2) { + Vector &vertices = polygon->_vertices; + size_t nn = polygon->_count; + + float width12 = x1 - x2, height12 = y1 - y2; + float det1 = x1 * y2 - y1 * x2; + float x3 = vertices[nn - 2], y3 = vertices[nn - 1]; + for (size_t ii = 0; ii < nn; ii += 2) { + float x4 = vertices[ii], y4 = vertices[ii + 1]; + float det2 = x3 * y4 - y3 * x4; + float width34 = x3 - x4, height34 = y3 - y4; + float det3 = width12 * height34 - height12 * width34; + float x = (det1 * width34 - width12 * det2) / det3; + if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { + float y = (det1 * height34 - height12 * det2) / det3; + if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) { + return true; + } + } + x3 = x4; + y3 = y4; + } + + return false; +} + +spine::Polygon *SkeletonBounds::getPolygon(BoundingBoxAttachment *attachment) { + int index = _boundingBoxes.indexOf(attachment); + return index == -1 ? NULL : _polygons[index]; +} + +BoundingBoxAttachment *SkeletonBounds::getBoundingBox(Polygon *polygon) { + int index = _polygons.indexOf(polygon); + return index == -1 ? NULL : _boundingBoxes[index]; +} + +Vector &SkeletonBounds::getPolygons() { + return _polygons; +} + +Vector &SkeletonBounds::getBoundingBoxes() { + return _boundingBoxes; +} + +float SkeletonBounds::getWidth() { + return _maxX - _minX; +} + +float SkeletonBounds::getHeight() { + return _maxY - _minY; +} + +void SkeletonBounds::aabbCompute() { + float minX = FLT_MAX; + float minY = FLT_MAX; + float maxX = FLT_MIN; + float maxY = FLT_MIN; + + for (size_t i = 0, n = _polygons.size(); i < n; ++i) { + spine::Polygon *polygon = _polygons[i]; + Vector &vertices = polygon->_vertices; + for (int ii = 0, nn = polygon->_count; ii < nn; ii += 2) { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = MathUtil::min(minX, x); + minY = MathUtil::min(minY, y); + maxX = MathUtil::max(maxX, x); + maxY = MathUtil::max(maxY, y); + } + } + _minX = minX; + _minY = minY; + _maxX = maxX; + _maxY = maxY; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SkeletonClipping.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonClipping.cpp new file mode 100644 index 000000000..a1681760d --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonClipping.cpp @@ -0,0 +1,397 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +using namespace spine; + +SkeletonClipping::SkeletonClipping() : _clipAttachment(NULL) { + _clipOutput.ensureCapacity(128); + _clippedVertices.ensureCapacity(128); + _clippedTriangles.ensureCapacity(128); + _clippedUVs.ensureCapacity(128); +} + +size_t SkeletonClipping::clipStart(Slot &slot, ClippingAttachment *clip) { + if (_clipAttachment != NULL) { + return 0; + } + + _clipAttachment = clip; + + int n = (int) clip->getWorldVerticesLength(); + _clippingPolygon.setSize(n, 0); + clip->computeWorldVertices(slot, 0, n, _clippingPolygon, 0, 2); + makeClockwise(_clippingPolygon); + _clippingPolygons = &_triangulator.decompose(_clippingPolygon, _triangulator.triangulate(_clippingPolygon)); + + for (size_t i = 0; i < _clippingPolygons->size(); ++i) { + Vector *polygonP = (*_clippingPolygons)[i]; + Vector &polygon = *polygonP; + makeClockwise(polygon); + polygon.add(polygon[0]); + polygon.add(polygon[1]); + } + + return (*_clippingPolygons).size(); +} + +void SkeletonClipping::clipEnd(Slot &slot) { + if (_clipAttachment != NULL && _clipAttachment->_endSlot == &slot._data) { + clipEnd(); + } +} + +void SkeletonClipping::clipEnd() { + if (_clipAttachment == NULL) return; + + _clipAttachment = NULL; + _clippingPolygons = NULL; + _clippedVertices.clear(); + _clippedUVs.clear(); + _clippedTriangles.clear(); + _clippingPolygon.clear(); +} + +void SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, + size_t trianglesLength) { + Vector &clipOutput = _clipOutput; + Vector &clippedVertices = _clippedVertices; + Vector &clippedTriangles = _clippedTriangles; + Vector *> &polygons = *_clippingPolygons; + size_t polygonsCount = (*_clippingPolygons).size(); + + size_t index = 0; + clippedVertices.clear(); + _clippedUVs.clear(); + clippedTriangles.clear(); + + int stride = 2; + size_t i = 0; +continue_outer: + for (; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] * stride; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] * stride; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] * stride; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + + for (size_t p = 0; p < polygonsCount; p++) { + size_t s = clippedVertices.size(); + if (clip(x1, y1, x2, y2, x3, y3, &(*polygons[p]), &clipOutput)) { + size_t clipOutputLength = clipOutput.size(); + if (clipOutputLength == 0) continue; + + size_t clipOutputCount = clipOutputLength >> 1; + clippedVertices.setSize(s + clipOutputCount * 2, 0); + for (size_t ii = 0; ii < clipOutputLength; ii += 2) { + float x = clipOutput[ii], y = clipOutput[ii + 1]; + clippedVertices[s] = x; + clippedVertices[s + 1] = y; + s += 2; + } + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0); + clipOutputCount--; + for (size_t ii = 1; ii < clipOutputCount; ii++) { + clippedTriangles[s] = (unsigned short) (index); + clippedTriangles[s + 1] = (unsigned short) (index + ii); + clippedTriangles[s + 2] = (unsigned short) (index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + } else { + clippedVertices.setSize(s + 3 * 2, 0); + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3, 0); + clippedTriangles[s] = (unsigned short) index; + clippedTriangles[s + 1] = (unsigned short) (index + 1); + clippedTriangles[s + 2] = (unsigned short) (index + 2); + index += 3; + i += 3; + goto continue_outer; + } + } + } +} + +void SkeletonClipping::clipTriangles(Vector &vertices, Vector &triangles, Vector &uvs, + size_t stride) { + clipTriangles(vertices.buffer(), triangles.buffer(), triangles.size(), uvs.buffer(), stride); +} + +void SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, + size_t trianglesLength, float *uvs, size_t stride) { + Vector &clipOutput = _clipOutput; + Vector &clippedVertices = _clippedVertices; + Vector &clippedTriangles = _clippedTriangles; + Vector *> &polygons = *_clippingPolygons; + size_t polygonsCount = (*_clippingPolygons).size(); + + size_t index = 0; + clippedVertices.clear(); + _clippedUVs.clear(); + clippedTriangles.clear(); + + size_t i = 0; +continue_outer: + for (; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] * (int) stride; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] * (int) stride; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] * (int) stride; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (size_t p = 0; p < polygonsCount; p++) { + size_t s = clippedVertices.size(); + if (clip(x1, y1, x2, y2, x3, y3, &(*polygons[p]), &clipOutput)) { + size_t clipOutputLength = clipOutput.size(); + if (clipOutputLength == 0) continue; + float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; + float d = 1 / (d0 * d2 + d1 * (y1 - y3)); + + size_t clipOutputCount = clipOutputLength >> 1; + clippedVertices.setSize(s + clipOutputCount * 2, 0); + _clippedUVs.setSize(s + clipOutputCount * 2, 0); + for (size_t ii = 0; ii < clipOutputLength; ii += 2) { + float x = clipOutput[ii], y = clipOutput[ii + 1]; + clippedVertices[s] = x; + clippedVertices[s + 1] = y; + float c0 = x - x3, c1 = y - y3; + float a = (d0 * c0 + d1 * c1) * d; + float b = (d4 * c0 + d2 * c1) * d; + float c = 1 - a - b; + _clippedUVs[s] = u1 * a + u2 * b + u3 * c; + _clippedUVs[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0); + clipOutputCount--; + for (size_t ii = 1; ii < clipOutputCount; ii++) { + clippedTriangles[s] = (unsigned short) (index); + clippedTriangles[s + 1] = (unsigned short) (index + ii); + clippedTriangles[s + 2] = (unsigned short) (index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + } else { + clippedVertices.setSize(s + 3 * 2, 0); + _clippedUVs.setSize(s + 3 * 2, 0); + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; + + _clippedUVs[s] = u1; + _clippedUVs[s + 1] = v1; + _clippedUVs[s + 2] = u2; + _clippedUVs[s + 3] = v2; + _clippedUVs[s + 4] = u3; + _clippedUVs[s + 5] = v3; + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3, 0); + clippedTriangles[s] = (unsigned short) index; + clippedTriangles[s + 1] = (unsigned short) (index + 1); + clippedTriangles[s + 2] = (unsigned short) (index + 2); + index += 3; + i += 3; + goto continue_outer; + } + } + } +} + +bool SkeletonClipping::isClipping() { + return _clipAttachment != NULL; +} + +Vector &SkeletonClipping::getClippedVertices() { + return _clippedVertices; +} + +Vector &SkeletonClipping::getClippedTriangles() { + return _clippedTriangles; +} + +Vector &SkeletonClipping::getClippedUVs() { + return _clippedUVs; +} + +bool SkeletonClipping::clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector *clippingArea, + Vector *output) { + Vector *originalOutput = output; + bool clipped = false; + + // Avoid copy at the end. + Vector *input; + if (clippingArea->size() % 4 >= 2) { + input = output; + output = &_scratch; + } else + input = &_scratch; + + input->clear(); + input->add(x1); + input->add(y1); + input->add(x2); + input->add(y2); + input->add(x3); + input->add(y3); + input->add(x1); + input->add(y1); + output->clear(); + + size_t clippingVerticesLast = clippingArea->size() - 4; + Vector &clippingVertices = *clippingArea; + for (size_t i = 0;; i += 2) { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3]; + + size_t outputStart = output->size(); + Vector &inputVertices = *input; + for (size_t ii = 0, nn = input->size() - 2; ii < nn;) { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + ii += 2; + float inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1]; + float s2 = ey * (edgeX - inputX2) > ex * (edgeY - inputY2); + float s1 = ey * (edgeX - inputX) - ex * (edgeY - inputY); + if (s1 > 0) { + if (s2) {// v1 inside, v2 inside + output->add(inputX2); + output->add(inputY2); + continue; + } + // v1 inside, v2 outside + float ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output->add(inputX + ix * t); + output->add(inputY + iy * t); + } else { + output->add(inputX2); + output->add(inputY2); + } + } else if (s2) {// v1 outside, v2 inside + float ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output->add(inputX + ix * t); + output->add(inputY + iy * t); + output->add(inputX2); + output->add(inputY2); + } else { + output->add(inputX2); + output->add(inputY2); + continue; + } + } + clipped = true; + } + + + if (outputStart == output->size()) { + // All edges outside. + originalOutput->clear(); + return true; + } + + output->add((*output)[0]); + output->add((*output)[1]); + + if (i == clippingVerticesLast) { + break; + } + Vector *temp = output; + output = input; + output->clear(); + input = temp; + } + + if (originalOutput != output) { + originalOutput->clear(); + for (size_t i = 0, n = output->size() - 2; i < n; ++i) + originalOutput->add((*output)[i]); + } else + originalOutput->setSize(originalOutput->size() - 2, 0); + + if (originalOutput->size() < 6) { + originalOutput->clear(); + return false; + } + return clipped; +} + +void SkeletonClipping::makeClockwise(Vector &polygon) { + size_t verticeslength = polygon.size(); + + float area = polygon[verticeslength - 2] * polygon[1] - polygon[0] * polygon[verticeslength - 1]; + float p1x, p1y, p2x, p2y; + + for (size_t i = 0, n = verticeslength - 3; i < n; i += 2) { + p1x = polygon[i]; + p1y = polygon[i + 1]; + p2x = polygon[i + 2]; + p2y = polygon[i + 3]; + area += p1x * p2y - p2x * p1y; + } + + if (area < 0) return; + + for (size_t i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { + float x = polygon[i], y = polygon[i + 1]; + int other = (int) (lastX - i); + polygon[i] = polygon[other]; + polygon[i + 1] = polygon[other + 1]; + polygon[other] = x; + polygon[other + 1] = y; + } +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SkeletonData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonData.cpp new file mode 100644 index 000000000..c3f4fd6e8 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonData.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +SkeletonData::SkeletonData() : _name(), + _defaultSkin(NULL), + _x(0), + _y(0), + _width(0), + _height(0), + _referenceScale(100), + _version(), + _hash(), + _fps(0), + _imagesPath() { +} + +SkeletonData::~SkeletonData() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_skins); + + _defaultSkin = NULL; + + ContainerUtil::cleanUpVectorOfPointers(_events); + ContainerUtil::cleanUpVectorOfPointers(_animations); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); + ContainerUtil::cleanUpVectorOfPointers(_physicsConstraints); + for (size_t i = 0; i < _strings.size(); i++) { + SpineExtension::free(_strings[i], __FILE__, __LINE__); + } +} + +BoneData *SkeletonData::findBone(const String &boneName) { + return ContainerUtil::findWithName(_bones, boneName); +} + +SlotData *SkeletonData::findSlot(const String &slotName) { + return ContainerUtil::findWithName(_slots, slotName); +} + +Skin *SkeletonData::findSkin(const String &skinName) { + return ContainerUtil::findWithName(_skins, skinName); +} + +spine::EventData *SkeletonData::findEvent(const String &eventDataName) { + return ContainerUtil::findWithName(_events, eventDataName); +} + +Animation *SkeletonData::findAnimation(const String &animationName) { + return ContainerUtil::findWithName(_animations, animationName); +} + +IkConstraintData *SkeletonData::findIkConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_ikConstraints, constraintName); +} + +TransformConstraintData *SkeletonData::findTransformConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_transformConstraints, constraintName); +} + +PathConstraintData *SkeletonData::findPathConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_pathConstraints, constraintName); +} + +PhysicsConstraintData *SkeletonData::findPhysicsConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_physicsConstraints, constraintName); +} + +const String &SkeletonData::getName() { + return _name; +} + +void SkeletonData::setName(const String &inValue) { + _name = inValue; +} + +Vector &SkeletonData::getBones() { + return _bones; +} + +Vector &SkeletonData::getSlots() { + return _slots; +} + +Vector &SkeletonData::getSkins() { + return _skins; +} + +Skin *SkeletonData::getDefaultSkin() { + return _defaultSkin; +} + +void SkeletonData::setDefaultSkin(Skin *inValue) { + _defaultSkin = inValue; +} + +Vector &SkeletonData::getEvents() { + return _events; +} + +Vector &SkeletonData::getAnimations() { + return _animations; +} + +Vector &SkeletonData::getIkConstraints() { + return _ikConstraints; +} + +Vector &SkeletonData::getTransformConstraints() { + return _transformConstraints; +} + +Vector &SkeletonData::getPathConstraints() { + return _pathConstraints; +} + +Vector &SkeletonData::getPhysicsConstraints() { + return _physicsConstraints; +} + +float SkeletonData::getX() { + return _x; +} + +void SkeletonData::setX(float inValue) { + _x = inValue; +} + +float SkeletonData::getY() { + return _y; +} + +void SkeletonData::setY(float inValue) { + _y = inValue; +} + +float SkeletonData::getWidth() { + return _width; +} + +void SkeletonData::setWidth(float inValue) { + _width = inValue; +} + +float SkeletonData::getHeight() { + return _height; +} + +void SkeletonData::setHeight(float inValue) { + _height = inValue; +} + +float SkeletonData::getReferenceScale() { + return _referenceScale; +} + +void SkeletonData::setReferenceScale(float inValue) { + _referenceScale = inValue; +} + +const String &SkeletonData::getVersion() { + return _version; +} + +void SkeletonData::setVersion(const String &inValue) { + _version = inValue; +} + +const String &SkeletonData::getHash() { + return _hash; +} + +void SkeletonData::setHash(const String &inValue) { + _hash = inValue; +} + +const String &SkeletonData::getImagesPath() { + return _imagesPath; +} + +void SkeletonData::setImagesPath(const String &inValue) { + _imagesPath = inValue; +} + + +const String &SkeletonData::getAudioPath() { + return _audioPath; +} + +void SkeletonData::setAudioPath(const String &inValue) { + _audioPath = inValue; +} + +float SkeletonData::getFps() { + return _fps; +} + +void SkeletonData::setFps(float inValue) { + _fps = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SkeletonJson.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonJson.cpp new file mode 100644 index 000000000..117fe55ee --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonJson.cpp @@ -0,0 +1,1616 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +static float toColor(const char *value, size_t index) { + char digits[3]; + char *error; + int color; + + if (index >= strlen(value) / 2) return -1; + + value += index * 2; + + digits[0] = *value; + digits[1] = *(value + 1); + digits[2] = '\0'; + color = (int) strtoul(digits, &error, 16); + if (*error != 0) return -1; + + return color / (float) 255; +} + +static void toColor(Color &color, const char *value, bool hasAlpha) { + color.r = toColor(value, 0); + color.g = toColor(value, 1); + color.b = toColor(value, 2); + if (hasAlpha) color.a = toColor(value, 3); +} + +SkeletonJson::SkeletonJson(Atlas *atlas) : _attachmentLoader(new (__FILE__, __LINE__) AtlasAttachmentLoader(atlas)), + _scale(1), _ownsLoader(true) {} + +SkeletonJson::SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader(attachmentLoader), + _scale(1), + _ownsLoader(ownsLoader) { + assert(_attachmentLoader != NULL); +} + +SkeletonJson::~SkeletonJson() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonJson::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *json = SpineExtension::readFile(path, &length); + if (length == 0 || !json) { + setError(NULL, "Unable to read skeleton file: ", path); + return NULL; + } + + skeletonData = readSkeletonData(json); + + SpineExtension::free(json, __FILE__, __LINE__); + + return skeletonData; +} + +SkeletonData *SkeletonJson::readSkeletonData(const char *json) { + int i, ii; + SkeletonData *skeletonData; + Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *path, *physics, *slots, *skins, *animations, *events; + + _error = ""; + _linkedMeshes.clear(); + + root = new (__FILE__, __LINE__) Json(json); + + if (!root) { + setError(NULL, "Invalid skeleton JSON: ", Json::getError()); + return NULL; + } + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + skeleton = Json::getItem(root, "skeleton"); + if (skeleton) { + skeletonData->_hash = Json::getString(skeleton, "hash", 0); + skeletonData->_version = Json::getString(skeleton, "spine", 0); + if (!skeletonData->_version.startsWith(SPINE_VERSION_STRING)) { + char errorMsg[255]; + snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->_version.buffer(), SPINE_VERSION_STRING); + delete skeletonData; + setError(NULL, errorMsg, ""); + return NULL; + } + skeletonData->_x = Json::getFloat(skeleton, "x", 0); + skeletonData->_y = Json::getFloat(skeleton, "y", 0); + skeletonData->_width = Json::getFloat(skeleton, "width", 0); + skeletonData->_height = Json::getFloat(skeleton, "height", 0); + skeletonData->_referenceScale = Json::getFloat(skeleton, "referenceScale", 100) * _scale; + skeletonData->_fps = Json::getFloat(skeleton, "fps", 30); + skeletonData->_audioPath = Json::getString(skeleton, "audio", 0); + skeletonData->_imagesPath = Json::getString(skeleton, "images", 0); + } + + /* Bones. */ + bones = Json::getItem(root, "bones"); + skeletonData->_bones.setSize(bones->_size, 0); + int bonesCount = 0; + for (boneMap = bones->_child, i = 0; boneMap; boneMap = boneMap->_next, ++i) { + BoneData *data; + const char *inherit; + + BoneData *parent = 0; + const char *parentName = Json::getString(boneMap, "parent", 0); + if (parentName) { + parent = skeletonData->findBone(parentName); + if (!parent) { + delete skeletonData; + setError(root, "Parent bone not found: ", parentName); + return NULL; + } + } + + data = new (__FILE__, __LINE__) BoneData(bonesCount, Json::getString(boneMap, "name", 0), parent); + + data->_length = Json::getFloat(boneMap, "length", 0) * _scale; + data->_x = Json::getFloat(boneMap, "x", 0) * _scale; + data->_y = Json::getFloat(boneMap, "y", 0) * _scale; + data->_rotation = Json::getFloat(boneMap, "rotation", 0); + data->_scaleX = Json::getFloat(boneMap, "scaleX", 1); + data->_scaleY = Json::getFloat(boneMap, "scaleY", 1); + data->_shearX = Json::getFloat(boneMap, "shearX", 0); + data->_shearY = Json::getFloat(boneMap, "shearY", 0); + inherit = Json::getString(boneMap, "inherit", "normal"); + data->_inherit = Inherit_Normal; + if (strcmp(inherit, "normal") == 0) data->_inherit = Inherit_Normal; + else if (strcmp(inherit, "onlyTranslation") == 0) + data->_inherit = Inherit_OnlyTranslation; + else if (strcmp(inherit, "noRotationOrReflection") == 0) + data->_inherit = Inherit_NoRotationOrReflection; + else if (strcmp(inherit, "noScale") == 0) + data->_inherit = Inherit_NoScale; + else if (strcmp(inherit, "noScaleOrReflection") == 0) + data->_inherit = Inherit_NoScaleOrReflection; + data->_skinRequired = Json::getBoolean(boneMap, "skin", false); + + const char *color = Json::getString(boneMap, "color", NULL); + if (color) toColor(data->getColor(), color, true); + + data->_icon = Json::getString(boneMap, "icon", ""); + data->_visible = Json::getBoolean(boneMap, "visible", true); + + skeletonData->_bones[i] = data; + bonesCount++; + } + + /* Slots. */ + slots = Json::getItem(root, "slots"); + if (slots) { + Json *slotMap; + skeletonData->_slots.ensureCapacity(slots->_size); + skeletonData->_slots.setSize(slots->_size, 0); + for (slotMap = slots->_child, i = 0; slotMap; slotMap = slotMap->_next, ++i) { + SlotData *data; + const char *color; + const char *dark; + Json *item; + + const char *boneName = Json::getString(slotMap, "bone", 0); + BoneData *boneData = skeletonData->findBone(boneName); + if (!boneData) { + delete skeletonData; + setError(root, "Slot bone not found: ", boneName); + return NULL; + } + + String slotName = String(Json::getString(slotMap, "name", 0)); + data = new (__FILE__, __LINE__) SlotData(i, slotName, *boneData); + + color = Json::getString(slotMap, "color", 0); + if (color) { + Color &c = data->getColor(); + c.r = toColor(color, 0); + c.g = toColor(color, 1); + c.b = toColor(color, 2); + c.a = toColor(color, 3); + } + + dark = Json::getString(slotMap, "dark", 0); + if (dark) { + Color &darkColor = data->getDarkColor(); + darkColor.r = toColor(dark, 0); + darkColor.g = toColor(dark, 1); + darkColor.b = toColor(dark, 2); + darkColor.a = 1; + data->setHasDarkColor(true); + } + + item = Json::getItem(slotMap, "attachment"); + if (item) data->setAttachmentName(item->_valueString); + + item = Json::getItem(slotMap, "blend"); + if (item) { + if (strcmp(item->_valueString, "additive") == 0) data->_blendMode = BlendMode_Additive; + else if (strcmp(item->_valueString, "multiply") == 0) + data->_blendMode = BlendMode_Multiply; + else if (strcmp(item->_valueString, "screen") == 0) + data->_blendMode = BlendMode_Screen; + } + data->_visible = Json::getBoolean(slotMap, "visible", true); + skeletonData->_slots[i] = data; + } + } + + /* IK constraints. */ + ik = Json::getItem(root, "ik"); + if (ik) { + Json *constraintMap; + skeletonData->_ikConstraints.ensureCapacity(ik->_size); + skeletonData->_ikConstraints.setSize(ik->_size, 0); + for (constraintMap = ik->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *targetName; + + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "IK bone not found: ", boneMap->_valueString); + return NULL; + } + } + + targetName = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(targetName); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", targetName); + return NULL; + } + + data->_mix = Json::getFloat(constraintMap, "mix", 1); + data->_softness = Json::getFloat(constraintMap, "softness", 0) * _scale; + data->_bendDirection = Json::getInt(constraintMap, "bendPositive", 1) ? 1 : -1; + data->_compress = Json::getInt(constraintMap, "compress", 0) ? true : false; + data->_stretch = Json::getInt(constraintMap, "stretch", 0) ? true : false; + data->_uniform = Json::getInt(constraintMap, "uniform", 0) ? true : false; + + skeletonData->_ikConstraints[i] = data; + } + } + + /* Transform constraints. */ + transform = Json::getItem(root, "transform"); + if (transform) { + Json *constraintMap; + skeletonData->_transformConstraints.ensureCapacity(transform->_size); + skeletonData->_transformConstraints.setSize(transform->_size, 0); + for (constraintMap = transform->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Transform bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", name); + return NULL; + } + + data->_local = Json::getInt(constraintMap, "local", 0) ? true : false; + data->_relative = Json::getInt(constraintMap, "relative", 0) ? true : false; + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_offsetX = Json::getFloat(constraintMap, "x", 0) * _scale; + data->_offsetY = Json::getFloat(constraintMap, "y", 0) * _scale; + data->_offsetScaleX = Json::getFloat(constraintMap, "scaleX", 0); + data->_offsetScaleY = Json::getFloat(constraintMap, "scaleY", 0); + data->_offsetShearY = Json::getFloat(constraintMap, "shearY", 0); + + data->_mixRotate = Json::getFloat(constraintMap, "mixRotate", 1); + data->_mixX = Json::getFloat(constraintMap, "mixX", 1); + data->_mixY = Json::getFloat(constraintMap, "mixY", data->_mixX); + data->_mixScaleX = Json::getFloat(constraintMap, "mixScaleX", 1); + data->_mixScaleY = Json::getFloat(constraintMap, "mixScaleY", data->_mixScaleX); + data->_mixShearY = Json::getFloat(constraintMap, "mixShearY", 1); + + skeletonData->_transformConstraints[i] = data; + } + } + + /* Path constraints */ + path = Json::getItem(root, "path"); + if (path) { + Json *constraintMap; + skeletonData->_pathConstraints.ensureCapacity(path->_size); + skeletonData->_pathConstraints.setSize(path->_size, 0); + for (constraintMap = path->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + const char *item; + + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Path bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findSlot(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target slot not found: ", name); + return NULL; + } + + item = Json::getString(constraintMap, "positionMode", "percent"); + if (strcmp(item, "fixed") == 0) { + data->_positionMode = PositionMode_Fixed; + } else if (strcmp(item, "percent") == 0) { + data->_positionMode = PositionMode_Percent; + } + + item = Json::getString(constraintMap, "spacingMode", "length"); + if (strcmp(item, "length") == 0) data->_spacingMode = SpacingMode_Length; + else if (strcmp(item, "fixed") == 0) + data->_spacingMode = SpacingMode_Fixed; + else if (strcmp(item, "percent") == 0) + data->_spacingMode = SpacingMode_Percent; + else + data->_spacingMode = SpacingMode_Proportional; + + item = Json::getString(constraintMap, "rotateMode", "tangent"); + if (strcmp(item, "tangent") == 0) data->_rotateMode = RotateMode_Tangent; + else if (strcmp(item, "chain") == 0) + data->_rotateMode = RotateMode_Chain; + else if (strcmp(item, "chainScale") == 0) + data->_rotateMode = RotateMode_ChainScale; + + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_position = Json::getFloat(constraintMap, "position", 0); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = Json::getFloat(constraintMap, "spacing", 0); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) + data->_spacing *= _scale; + data->_mixRotate = Json::getFloat(constraintMap, "mixRotate", 1); + data->_mixX = Json::getFloat(constraintMap, "mixX", 1); + data->_mixY = Json::getFloat(constraintMap, "mixY", data->_mixX); + + skeletonData->_pathConstraints[i] = data; + } + } + + /* Physics constraints */ + physics = Json::getItem(root, "physics"); + if (physics) { + Json *constraintMap; + skeletonData->_physicsConstraints.ensureCapacity(physics->_size); + skeletonData->_physicsConstraints.setSize(physics->_size, 0); + for (constraintMap = physics->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + + PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + name = Json::getString(constraintMap, "bone", 0); + data->_bone = skeletonData->findBone(name); + if (!data->_bone) { + delete skeletonData; + setError(root, "Physics bone not found: ", name); + return NULL; + } + + data->_x = Json::getFloat(constraintMap, "x", 0); + data->_y = Json::getFloat(constraintMap, "y", 0); + data->_rotate = Json::getFloat(constraintMap, "rotate", 0); + data->_scaleX = Json::getFloat(constraintMap, "scaleX", 0); + data->_shearX = Json::getFloat(constraintMap, "shearX", 0); + data->_limit = Json::getFloat(constraintMap, "limit", 5000) * _scale; + data->_step = 1.0f / Json::getInt(constraintMap, "fps", 60); + data->_inertia = Json::getFloat(constraintMap, "inertia", 1); + data->_strength = Json::getFloat(constraintMap, "strength", 100); + data->_damping = Json::getFloat(constraintMap, "damping", 1); + data->_massInverse = 1.0f / Json::getFloat(constraintMap, "mass", 1); + data->_wind = Json::getFloat(constraintMap, "wind", 0); + data->_gravity = Json::getFloat(constraintMap, "gravity", 0); + data->_mix = Json::getFloat(constraintMap, "mix", 1); + data->_inertiaGlobal = Json::getBoolean(constraintMap, "inertiaGlobal", false); + data->_strengthGlobal = Json::getBoolean(constraintMap, "strengthGlobal", false); + data->_dampingGlobal = Json::getBoolean(constraintMap, "dampingGlobal", false); + data->_massGlobal = Json::getBoolean(constraintMap, "massGlobal", false); + data->_windGlobal = Json::getBoolean(constraintMap, "windGlobal", false); + data->_gravityGlobal = Json::getBoolean(constraintMap, "gravityGlobal", false); + data->_mixGlobal = Json::getBoolean(constraintMap, "mixGlobal", false); + + skeletonData->_physicsConstraints[i] = data; + } + } + + /* Skins. */ + skins = Json::getItem(root, "skins"); + if (skins) { + Json *skinMap; + skeletonData->_skins.ensureCapacity(skins->_size); + skeletonData->_skins.setSize(skins->_size, 0); + int skinsIndex = 0; + for (skinMap = skins->_child, i = 0; skinMap; skinMap = skinMap->_next, ++i) { + Json *attachmentsMap; + Json *curves; + + Skin *skin = new (__FILE__, __LINE__) Skin(Json::getString(skinMap, "name", "")); + + Json *item = Json::getItem(skinMap, "bones"); + if (item) { + for (item = item->_child; item; item = item->_next) { + BoneData *data = skeletonData->findBone(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin bone not found: "), item->_valueString); + return NULL; + } + skin->getBones().add(data); + } + } + + item = Json::getItem(skinMap, "ik"); + if (item) { + for (item = item->_child; item; item = item->_next) { + IkConstraintData *data = skeletonData->findIkConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin IK constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "transform"); + if (item) { + for (item = item->_child; item; item = item->_next) { + TransformConstraintData *data = skeletonData->findTransformConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin transform constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "path"); + if (item) { + for (item = item->_child; item; item = item->_next) { + PathConstraintData *data = skeletonData->findPathConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin path constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "physics"); + if (item) { + for (item = item->_child; item; item = item->_next) { + PhysicsConstraintData *data = skeletonData->findPhysicsConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin physics constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + skeletonData->_skins[skinsIndex++] = skin; + if (strcmp(Json::getString(skinMap, "name", ""), "default") == 0) { + skeletonData->_defaultSkin = skin; + } + + Json *attachments = Json::getItem(skinMap, "attachments"); + if (attachments) + for (attachmentsMap = attachments->_child; + attachmentsMap; attachmentsMap = attachmentsMap->_next) { + SlotData *slot = skeletonData->findSlot(attachmentsMap->_name); + Json *attachmentMap; + + for (attachmentMap = attachmentsMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { + Attachment *attachment = NULL; + const char *skinAttachmentName = attachmentMap->_name; + const char *attachmentName = Json::getString(attachmentMap, "name", skinAttachmentName); + const char *attachmentPath = Json::getString(attachmentMap, "path", attachmentName); + const char *color; + Json *entry; + + const char *typeString = Json::getString(attachmentMap, "type", "region"); + AttachmentType type; + if (strcmp(typeString, "region") == 0) type = AttachmentType_Region; + else if (strcmp(typeString, "mesh") == 0) + type = AttachmentType_Mesh; + else if (strcmp(typeString, "linkedmesh") == 0) + type = AttachmentType_Linkedmesh; + else if (strcmp(typeString, "boundingbox") == 0) + type = AttachmentType_Boundingbox; + else if (strcmp(typeString, "path") == 0) + type = AttachmentType_Path; + else if (strcmp(typeString, "clipping") == 0) + type = AttachmentType_Clipping; + else if (strcmp(typeString, "point") == 0) + type = AttachmentType_Point; + else { + delete skeletonData; + setError(root, "Unknown attachment type: ", typeString); + return NULL; + } + + switch (type) { + case AttachmentType_Region: { + Sequence *sequence = readSequence(Json::getItem(attachmentMap, "sequence")); + attachment = _attachmentLoader->newRegionAttachment(*skin, attachmentName, attachmentPath, sequence); + if (!attachment) { + delete skeletonData; + setError(root, "Error reading attachment: ", skinAttachmentName); + return NULL; + } + + RegionAttachment *region = static_cast(attachment); + region->_path = attachmentPath; + + region->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + region->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + region->_scaleX = Json::getFloat(attachmentMap, "scaleX", 1); + region->_scaleY = Json::getFloat(attachmentMap, "scaleY", 1); + region->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + region->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + region->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + region->_sequence = sequence; + + color = Json::getString(attachmentMap, "color", 0); + if (color) toColor(region->getColor(), color, true); + + if (region->_region != NULL) region->updateRegion(); + _attachmentLoader->configureAttachment(region); + break; + } + case AttachmentType_Mesh: + case AttachmentType_Linkedmesh: { + Sequence *sequence = readSequence(Json::getItem(attachmentMap, "sequence")); + attachment = _attachmentLoader->newMeshAttachment(*skin, attachmentName, attachmentPath, sequence); + + if (!attachment) { + delete skeletonData; + setError(root, "Error reading attachment: ", skinAttachmentName); + return NULL; + } + + MeshAttachment *mesh = static_cast(attachment); + mesh->_path = attachmentPath; + + color = Json::getString(attachmentMap, "color", 0); + if (color) toColor(mesh->getColor(), color, true); + + mesh->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + mesh->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + mesh->_sequence = sequence; + + entry = Json::getItem(attachmentMap, "parent"); + if (!entry) { + int verticesLength; + entry = Json::getItem(attachmentMap, "triangles"); + mesh->_triangles.ensureCapacity(entry->_size); + mesh->_triangles.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_triangles[ii] = (unsigned short) entry->_valueInt; + + entry = Json::getItem(attachmentMap, "uvs"); + verticesLength = entry->_size; + mesh->_regionUVs.ensureCapacity(verticesLength); + mesh->_regionUVs.setSize(verticesLength, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_regionUVs[ii] = entry->_valueFloat; + + readVertices(attachmentMap, mesh, verticesLength); + + if (mesh->_region != NULL) mesh->updateRegion(); + + mesh->_hullLength = Json::getInt(attachmentMap, "hull", 0); + + entry = Json::getItem(attachmentMap, "edges"); + if (entry) { + mesh->_edges.ensureCapacity(entry->_size); + mesh->_edges.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_edges[ii] = entry->_valueInt; + } + _attachmentLoader->configureAttachment(mesh); + } else { + bool inheritTimelines = Json::getInt(attachmentMap, "timelines", 1) ? true : false; + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, + String(Json::getString( + attachmentMap, + "skin", 0)), + slot->getIndex(), + String(entry->_valueString), + inheritTimelines); + _linkedMeshes.add(linkedMesh); + } + break; + } + case AttachmentType_Boundingbox: { + attachment = _attachmentLoader->newBoundingBoxAttachment(*skin, attachmentName); + + BoundingBoxAttachment *box = static_cast(attachment); + + int vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, box, vertexCount); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(box->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Path: { + attachment = _attachmentLoader->newPathAttachment(*skin, attachmentName); + + PathAttachment *pathAttatchment = static_cast(attachment); + + int vertexCount = 0; + pathAttatchment->_closed = Json::getInt(attachmentMap, "closed", 0) ? true : false; + pathAttatchment->_constantSpeed = Json::getInt(attachmentMap, "constantSpeed", 1) ? true + : false; + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0); + readVertices(attachmentMap, pathAttatchment, vertexCount << 1); + + pathAttatchment->_lengths.ensureCapacity(vertexCount / 3); + pathAttatchment->_lengths.setSize(vertexCount / 3, 0); + + curves = Json::getItem(attachmentMap, "lengths"); + for (curves = curves->_child, ii = 0; curves; curves = curves->_next, ++ii) + pathAttatchment->_lengths[ii] = curves->_valueFloat * _scale; + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(pathAttatchment->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Point: { + attachment = _attachmentLoader->newPointAttachment(*skin, attachmentName); + + PointAttachment *point = static_cast(attachment); + + point->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + point->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + point->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(point->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Clipping: { + attachment = _attachmentLoader->newClippingAttachment(*skin, attachmentName); + + ClippingAttachment *clip = static_cast(attachment); + + int vertexCount = 0; + const char *end = Json::getString(attachmentMap, "end", 0); + if (end) clip->_endSlot = skeletonData->findSlot(end); + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, clip, vertexCount); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(clip->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + } + + skin->setAttachment(slot->getIndex(), skinAttachmentName, attachment); + } + } + } + } + + /* Linked meshes. */ + int n = (int) _linkedMeshes.size(); + for (i = 0; i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = linkedMesh->_skin.length() == 0 ? skeletonData->getDefaultSkin() : skeletonData->findSkin(linkedMesh->_skin); + if (skin == NULL) { + delete skeletonData; + setError(root, "Skin not found: ", linkedMesh->_skin.buffer()); + return NULL; + } + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + delete skeletonData; + setError(root, "Parent mesh not found: ", linkedMesh->_parent.buffer()); + return NULL; + } + linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimeline ? static_cast(parent) + : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + if (linkedMesh->_mesh->_region != NULL) linkedMesh->_mesh->updateRegion(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + events = Json::getItem(root, "events"); + if (events) { + Json *eventMap; + skeletonData->_events.ensureCapacity(events->_size); + skeletonData->_events.setSize(events->_size, 0); + for (eventMap = events->_child, i = 0; eventMap; eventMap = eventMap->_next, ++i) { + EventData *eventData = new (__FILE__, __LINE__) EventData(String(eventMap->_name)); + + eventData->_intValue = Json::getInt(eventMap, "int", 0); + eventData->_floatValue = Json::getFloat(eventMap, "float", 0); + const char *stringValue = Json::getString(eventMap, "string", 0); + eventData->_stringValue = stringValue; + const char *audioPath = Json::getString(eventMap, "audio", 0); + eventData->_audioPath = audioPath; + if (audioPath) { + eventData->_volume = Json::getFloat(eventMap, "volume", 1); + eventData->_balance = Json::getFloat(eventMap, "balance", 0); + } + skeletonData->_events[i] = eventData; + } + } + + /* Animations. */ + animations = Json::getItem(root, "animations"); + if (animations) { + Json *animationMap; + skeletonData->_animations.ensureCapacity(animations->_size); + skeletonData->_animations.setSize(animations->_size, 0); + int animationsIndex = 0; + for (animationMap = animations->_child; animationMap; animationMap = animationMap->_next) { + Animation *animation = readAnimation(animationMap, skeletonData); + if (!animation) { + delete skeletonData; + delete root; + return NULL; + } + skeletonData->_animations[animationsIndex++] = animation; + } + } + + delete root; + + return skeletonData; +} + +Sequence *SkeletonJson::readSequence(Json *item) { + if (item == NULL) return NULL; + Sequence *sequence = new Sequence(Json::getInt(item, "count", 0)); + sequence->_start = Json::getInt(item, "start", 1); + sequence->_digits = Json::getInt(item, "digits", 0); + sequence->_setupIndex = Json::getInt(item, "setupIndex", 0); + return sequence; +} + +void SkeletonJson::setBezier(CurveTimeline *timeline, int frame, int value, int bezier, float time1, float value1, float cx1, + float cy1, + float cx2, float cy2, float time2, float value2) { + timeline->setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); +} + +int SkeletonJson::readCurve(Json *curve, CurveTimeline *timeline, int bezier, int frame, int value, float time1, + float time2, + float value1, float value2, float scale) { + if (curve->_type == Json::JSON_STRING && strcmp(curve->_valueString, "stepped") == 0) { + timeline->setStepped(frame); + return bezier; + } + curve = Json::getItem(curve, value << 2); + float cx1 = curve->_valueFloat; + curve = curve->_next; + float cy1 = curve->_valueFloat * scale; + curve = curve->_next; + float cx2 = curve->_valueFloat; + curve = curve->_next; + float cy2 = curve->_valueFloat * scale; + setBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; +} + +Timeline *SkeletonJson::readTimeline(Json *keyMap, CurveTimeline1 *timeline, float defaultValue, float scale) { + float time = Json::getFloat(keyMap, "time", 0); + float value = Json::getFloat(keyMap, "value", defaultValue) * scale; + int bezier = 0; + for (int frame = 0;; frame++) { + timeline->setFrame(frame, time, value); + Json *nextMap = keyMap->_next; + if (!nextMap) break; + float time2 = Json::getFloat(nextMap, "time", 0); + float value2 = Json::getFloat(nextMap, "value", defaultValue) * scale; + Json *curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + time = time2; + value = value2; + keyMap = nextMap; + } + // timeline.shrink(); // BOZO + return timeline; +} + +Timeline *SkeletonJson::readTimeline(Json *keyMap, CurveTimeline2 *timeline, const char *name1, const char *name2, + float defaultValue, float scale) { + float time = Json::getFloat(keyMap, "time", 0); + float value1 = Json::getFloat(keyMap, name1, defaultValue) * scale; + float value2 = Json::getFloat(keyMap, name2, defaultValue) * scale; + int bezier = 0; + for (int frame = 0;; frame++) { + timeline->setFrame(frame, time, value1, value2); + Json *nextMap = keyMap->_next; + if (!nextMap) break; + float time2 = Json::getFloat(nextMap, "time", 0); + float nvalue1 = Json::getFloat(nextMap, name1, defaultValue) * scale; + float nvalue2 = Json::getFloat(nextMap, name2, defaultValue) * scale; + Json *curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + keyMap = nextMap; + } + // timeline.shrink(); // BOZO + return timeline; +} + +int SkeletonJson::findSlotIndex(SkeletonData *skeletonData, const String &slotName, Vector timelines) { + int slotIndex = ContainerUtil::findIndexWithName(skeletonData->getSlots(), slotName); + if (slotIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Slot not found: ", slotName); + } + return slotIndex; +} + +Animation *SkeletonJson::readAnimation(Json *root, SkeletonData *skeletonData) { + Vector timelines; + Json *bones = Json::getItem(root, "bones"); + Json *slots = Json::getItem(root, "slots"); + Json *ik = Json::getItem(root, "ik"); + Json *transform = Json::getItem(root, "transform"); + Json *paths = Json::getItem(root, "path"); + Json *physics = Json::getItem(root, "physics"); + Json *attachments = Json::getItem(root, "attachments"); + Json *drawOrder = Json::getItem(root, "drawOrder"); + Json *events = Json::getItem(root, "events"); + Json *boneMap, *slotMap, *keyMap, *nextMap, *curve; + int frame, bezier; + Color color, color2, newColor, newColor2; + + /** Slot timelines. */ + for (slotMap = slots ? slots->_child : 0; slotMap; slotMap = slotMap->_next) { + int slotIndex = findSlotIndex(skeletonData, slotMap->_name, timelines); + if (slotIndex == -1) return NULL; + + for (Json *timelineMap = slotMap->_child; timelineMap; timelineMap = timelineMap->_next) { + int frames = timelineMap->_size; + if (strcmp(timelineMap->_name, "attachment") == 0) { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frames, slotIndex); + for (keyMap = timelineMap->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), + Json::getItem(keyMap, "name") ? Json::getItem(keyMap, "name")->_valueString : NULL); + } + timelines.add(timeline); + + } else if (strcmp(timelineMap->_name, "rgba") == 0) { + RGBATimeline *timeline = new (__FILE__, __LINE__) RGBATimeline(frames, frames << 2, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "color", 0), true); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "color", 0), true); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1); + } + time = time2; + color = newColor; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "rgb") == 0) { + RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frames, frames * 3, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "color", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "color", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + } + time = time2; + color = newColor; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "alpha") == 0) { + timelines.add(readTimeline(timelineMap->_child, + new (__FILE__, __LINE__) AlphaTimeline(frames, frames, slotIndex), + 0, 1)); + } else if (strcmp(timelineMap->_name, "rgba2") == 0) { + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frames, frames * 7, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "light", 0), true); + toColor(color2, Json::getString(keyMap, "dark", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "light", 0), true); + toColor(newColor2, Json::getString(nextMap, "dark", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, color2.b, newColor2.b, 1); + } + time = time2; + color = newColor; + color2 = newColor2; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "rgb2") == 0) { + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frames, frames * 6, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "light", 0), false); + toColor(color2, Json::getString(keyMap, "dark", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "light", 0), false); + toColor(newColor2, Json::getString(nextMap, "dark", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color2.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.b, newColor2.b, 1); + } + time = time2; + color = newColor; + color2 = newColor2; + keyMap = nextMap; + } + timelines.add(timeline); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a slot: ", timelineMap->_name); + return NULL; + } + } + } + + /** Bone timelines. */ + for (boneMap = bones ? bones->_child : 0; boneMap; boneMap = boneMap->_next) { + int boneIndex = ContainerUtil::findIndexWithName(skeletonData->_bones, boneMap->_name); + if (boneIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Bone not found: ", boneMap->_name); + return NULL; + } + + for (Json *timelineMap = boneMap->_child; timelineMap; timelineMap = timelineMap->_next) { + int frames = timelineMap->_size; + if (frames == 0) continue; + + if (strcmp(timelineMap->_name, "rotate") == 0) { + timelines.add(readTimeline(timelineMap->_child, + new RotateTimeline(frames, frames, boneIndex), 0, + 1)); + } else if (strcmp(timelineMap->_name, "translate") == 0) { + TranslateTimeline *timeline = new TranslateTimeline(frames, frames << 1, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 0, _scale)); + } else if (strcmp(timelineMap->_name, "translatex") == 0) { + TranslateXTimeline *timeline = new TranslateXTimeline(frames, frames, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, _scale)); + } else if (strcmp(timelineMap->_name, "translatey") == 0) { + TranslateYTimeline *timeline = new TranslateYTimeline(frames, frames, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, _scale)); + } else if (strcmp(timelineMap->_name, "scale") == 0) { + ScaleTimeline *timeline = new (__FILE__, __LINE__) ScaleTimeline(frames, + frames << 1, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 1, 1)); + } else if (strcmp(timelineMap->_name, "scalex") == 0) { + ScaleXTimeline *timeline = new (__FILE__, __LINE__) ScaleXTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 1, 1)); + } else if (strcmp(timelineMap->_name, "scaley") == 0) { + ScaleYTimeline *timeline = new (__FILE__, __LINE__) ScaleYTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 1, 1)); + } else if (strcmp(timelineMap->_name, "shear") == 0) { + ShearTimeline *timeline = new (__FILE__, __LINE__) ShearTimeline(frames, + frames << 1, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 0, 1)); + } else if (strcmp(timelineMap->_name, "shearx") == 0) { + ShearXTimeline *timeline = new (__FILE__, __LINE__) ShearXTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, 1)); + } else if (strcmp(timelineMap->_name, "sheary") == 0) { + ShearYTimeline *timeline = new (__FILE__, __LINE__) ShearYTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, 1)); + } else if (strcmp(timelineMap->_name, "inherit") == 0) { + InheritTimeline *timeline = new (__FILE__, __LINE__) InheritTimeline(frames, boneIndex); + keyMap = timelineMap->_child; + for (frame = 0;; frame++) { + float time = Json::getFloat(keyMap, "time", 0); + const char *value = Json::getString(keyMap, "inherit", "normal"); + Inherit inherit = Inherit_Normal; + if (strcmp(value, "normal") == 0) inherit = Inherit_Normal; + else if (strcmp(value, "onlyTranslation") == 0) + inherit = Inherit_OnlyTranslation; + else if (strcmp(value, "noRotationOrReflection") == 0) + inherit = Inherit_NoRotationOrReflection; + else if (strcmp(value, "noScale") == 0) + inherit = Inherit_NoScale; + else if (strcmp(value, "noScaleOrReflection") == 0) + inherit = Inherit_NoScaleOrReflection; + timeline->setFrame(frame, time, inherit); + nextMap = keyMap->_next; + if (!nextMap) break; + } + timelines.add(timeline); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a bone: ", timelineMap->_name); + return NULL; + } + } + } + + /** IK constraint timelines. */ + for (Json *constraintMap = ik ? ik->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + keyMap = constraintMap->_child; + if (keyMap == NULL) continue; + + IkConstraintData *constraint = skeletonData->findIkConstraint(constraintMap->_name); + int constraintIndex = skeletonData->_ikConstraints.indexOf(constraint); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(constraintMap->_size, + constraintMap->_size << 1, + constraintIndex); + + float time = Json::getFloat(keyMap, "time", 0); + float mix = Json::getFloat(keyMap, "mix", 1); + float softness = Json::getFloat(keyMap, "softness", 0) * _scale; + + for (frame = 0, bezier = 0;; frame++) { + int bendDirection = Json::getBoolean(keyMap, "bendPositive", true) ? 1 : -1; + timeline->setFrame(frame, time, mix, softness, bendDirection, Json::getBoolean(keyMap, "compress", false), + Json::getBoolean(keyMap, "stretch", false)); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + + float time2 = Json::getFloat(nextMap, "time", 0); + float mix2 = Json::getFloat(nextMap, "mix", 1); + float softness2 = Json::getFloat(nextMap, "softness", 0) * _scale; + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, _scale); + } + + time = time2; + mix = mix2; + softness = softness2; + keyMap = nextMap; + } + + timelines.add(timeline); + } + + /** Transform constraint timelines. */ + for (Json *constraintMap = transform ? transform->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + keyMap = constraintMap->_child; + if (keyMap == NULL) continue; + + TransformConstraintData *constraint = skeletonData->findTransformConstraint(constraintMap->_name); + int constraintIndex = skeletonData->_transformConstraints.indexOf(constraint); + TransformConstraintTimeline *timeline = new (__FILE__, __LINE__) TransformConstraintTimeline( + constraintMap->_size, constraintMap->_size * 6, constraintIndex); + + float time = Json::getFloat(keyMap, "time", 0); + float mixRotate = Json::getFloat(keyMap, "mixRotate", 1); + float mixShearY = Json::getFloat(keyMap, "mixShearY", 1); + float mixX = Json::getFloat(keyMap, "mixX", 1); + float mixY = Json::getFloat(keyMap, "mixY", mixX); + float mixScaleX = Json::getFloat(keyMap, "mixScaleX", 1); + float mixScaleY = Json::getFloat(keyMap, "mixScaleY", mixScaleX); + + for (frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + + float time2 = Json::getFloat(nextMap, "time", 0); + float mixRotate2 = Json::getFloat(nextMap, "mixRotate", 1); + float mixShearY2 = Json::getFloat(nextMap, "mixShearY", 1); + float mixX2 = Json::getFloat(nextMap, "mixX", 1); + float mixY2 = Json::getFloat(nextMap, "mixY", mixX2); + float mixScaleX2 = Json::getFloat(nextMap, "mixScaleX", 1); + float mixScaleY2 = Json::getFloat(nextMap, "mixScaleY", mixScaleX2); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixScaleX = mixScaleX2; + keyMap = nextMap; + } + + timelines.add(timeline); + } + + /** Path constraint timelines. */ + for (Json *constraintMap = paths ? paths->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + PathConstraintData *constraint = skeletonData->findPathConstraint(constraintMap->_name); + if (!constraint) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Path constraint not found: ", constraintMap->_name); + return NULL; + } + int constraintIndex = skeletonData->_pathConstraints.indexOf(constraint); + for (Json *timelineMap = constraintMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + const char *timelineName = timelineMap->_name; + int frames = timelineMap->_size; + if (strcmp(timelineName, "position") == 0) { + PathConstraintPositionTimeline *timeline = new (__FILE__, __LINE__) PathConstraintPositionTimeline( + frames, frames, constraintIndex); + timelines.add( + readTimeline(keyMap, timeline, 0, constraint->_positionMode == PositionMode_Fixed ? _scale : 1)); + } else if (strcmp(timelineName, "spacing") == 0) { + CurveTimeline1 *timeline = new PathConstraintSpacingTimeline(frames, frames, + constraintIndex); + timelines.add(readTimeline(keyMap, timeline, 0, + constraint->_spacingMode == SpacingMode_Length || + constraint->_spacingMode == SpacingMode_Fixed + ? _scale + : 1)); + } else if (strcmp(timelineName, "mix") == 0) { + PathConstraintMixTimeline *timeline = new PathConstraintMixTimeline(frames, + frames * 3, constraintIndex); + float time = Json::getFloat(keyMap, "time", 0); + float mixRotate = Json::getFloat(keyMap, "mixRotate", 1); + float mixX = Json::getFloat(keyMap, "mixX", 1); + float mixY = Json::getFloat(keyMap, "mixY", mixX); + for (frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + float mixRotate2 = Json::getFloat(nextMap, "mixRotate", 1); + float mixX2 = Json::getFloat(nextMap, "mixX", 1); + float mixY2 = Json::getFloat(nextMap, "mixY", mixX2); + curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.add(timeline); + } + } + } + + /** Physics constraint timelines. */ + for (Json *constraintMap = physics ? physics->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + int index = -1; + if (constraintMap->_name && strlen(constraintMap->_name) > 0) { + PhysicsConstraintData *constraint = skeletonData->findPhysicsConstraint(constraintMap->_name); + if (!constraint) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Physics constraint not found: ", constraintMap->_name); + return NULL; + } + index = skeletonData->_physicsConstraints.indexOf(constraint); + } + for (Json *timelineMap = constraintMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + const char *timelineName = timelineMap->_name; + int frames = timelineMap->_size; + if (strcmp(timelineName, "reset") == 0) { + PhysicsConstraintResetTimeline *timeline = new (__FILE__, __LINE__) PhysicsConstraintResetTimeline(frames, index); + for (frame = 0; keyMap != nullptr; keyMap = keyMap->_next, frame++) { + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0)); + } + timelines.add(timeline); + continue; + } + + CurveTimeline1 *timeline = nullptr; + if (strcmp(timelineName, "inertia") == 0) { + timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index); + } else if (strcmp(timelineName, "strength") == 0) { + timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index); + } else if (strcmp(timelineName, "damping") == 0) { + timeline = new PhysicsConstraintDampingTimeline(frames, frames, index); + } else if (strcmp(timelineName, "mass") == 0) { + timeline = new PhysicsConstraintMassTimeline(frames, frames, index); + } else if (strcmp(timelineName, "wind") == 0) { + timeline = new PhysicsConstraintWindTimeline(frames, frames, index); + } else if (strcmp(timelineName, "gravity") == 0) { + timeline = new PhysicsConstraintGravityTimeline(frames, frames, index); + } else if (strcmp(timelineName, "mix") == 0) { + timeline = new PhysicsConstraintMixTimeline(frames, frames, index); + } else { + continue; + } + timelines.add(readTimeline(keyMap, timeline, 0, 1)); + } + } + + /** Attachment timelines. */ + for (Json *attachmenstMap = attachments ? attachments->_child : NULL; attachmenstMap; attachmenstMap = attachmenstMap->_next) { + Skin *skin = skeletonData->findSkin(attachmenstMap->_name); + for (slotMap = attachmenstMap->_child; slotMap; slotMap = slotMap->_next) { + int slotIndex = findSlotIndex(skeletonData, slotMap->_name, timelines); + if (slotIndex == -1) return NULL; + + for (Json *attachmentMap = slotMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { + Attachment *attachment = skin->getAttachment(slotIndex, attachmentMap->_name); + if (!attachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Attachment not found: ", attachmentMap->_name); + return NULL; + } + + for (Json *timelineMap = attachmentMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + int frames = timelineMap->_size; + String timelineName = timelineMap->_name; + if (timelineName == "deform") { + VertexAttachment *vertexAttachment = static_cast(attachment); + bool weighted = vertexAttachment->_bones.size() != 0; + Vector &verts = vertexAttachment->_vertices; + int deformLength = weighted ? (int) verts.size() / 3 * 2 : (int) verts.size(); + + DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frames, + frames, slotIndex, vertexAttachment); + float time = Json::getFloat(keyMap, "time", 0); + for (frame = 0, bezier = 0;; frame++) { + Json *vertices = Json::getItem(keyMap, "vertices"); + Vector deformed; + if (!vertices) { + if (weighted) { + deformed.setSize(deformLength, 0); + } else { + deformed.clearAndAddAll(vertexAttachment->_vertices); + } + } else { + deformed.setSize(deformLength, 0); + int v, start = Json::getInt(keyMap, "offset", 0); + Json *vertex; + if (_scale == 1) { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat; + } + } else { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat * _scale; + } + } + if (!weighted) { + Vector &verticesAttachment = vertexAttachment->_vertices; + for (v = 0; v < deformLength; ++v) { + deformed[v] += verticesAttachment[v]; + } + } + } + timeline->setFrame(frame, time, deformed); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (timelineName == "sequence") { + SequenceTimeline *timeline = new SequenceTimeline(frames, slotIndex, attachment); + float lastDelay = 0; + for (frame = 0; keyMap != NULL; keyMap = keyMap->_next, frame++) { + float delay = Json::getFloat(keyMap, "delay", lastDelay); + float time = Json::getFloat(keyMap, "time", 0); + String modeString = Json::getString(keyMap, "mode", "hold"); + int index = Json::getInt(keyMap, "index", 0); + SequenceMode mode = SequenceMode::hold; + if (modeString == "once") mode = SequenceMode::once; + if (modeString == "loop") mode = SequenceMode::loop; + if (modeString == "pingpong") mode = SequenceMode::pingpong; + if (modeString == "onceReverse") mode = SequenceMode::onceReverse; + if (modeString == "loopReverse") mode = SequenceMode::loopReverse; + if (modeString == "pingpongReverse") mode = SequenceMode::pingpongReverse; + timeline->setFrame(frame, time, mode, index, delay); + lastDelay = delay; + } + timelines.add(timeline); + } + } + } + } + } + + /** Draw order timeline. */ + if (drawOrder) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrder->_size); + + for (keyMap = drawOrder->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + int ii; + Vector drawOrder2; + Json *offsets = Json::getItem(keyMap, "offsets"); + if (offsets) { + Json *offsetMap; + Vector unchanged; + unchanged.ensureCapacity(skeletonData->_slots.size() - offsets->_size); + unchanged.setSize(skeletonData->_slots.size() - offsets->_size, 0); + size_t originalIndex = 0, unchangedIndex = 0; + + drawOrder2.ensureCapacity(skeletonData->_slots.size()); + drawOrder2.setSize(skeletonData->_slots.size(), 0); + for (ii = (int) skeletonData->_slots.size() - 1; ii >= 0; --ii) + drawOrder2[ii] = -1; + + for (offsetMap = offsets->_child; offsetMap; offsetMap = offsetMap->_next) { + int slotIndex = findSlotIndex(skeletonData, Json::getString(offsetMap, "slot", 0), timelines); + if (slotIndex == -1) return NULL; + + /* Collect unchanged items. */ + while (originalIndex != (size_t) slotIndex) + unchanged[unchangedIndex++] = (int) originalIndex++; + /* Set changed items. */ + drawOrder2[originalIndex + Json::getInt(offsetMap, "offset", 0)] = (int) originalIndex; + originalIndex++; + } + /* Collect remaining unchanged items. */ + while ((int) originalIndex < (int) skeletonData->_slots.size()) + unchanged[unchangedIndex++] = (int) originalIndex++; + /* Fill in unchanged items. */ + for (ii = (int) skeletonData->_slots.size() - 1; ii >= 0; ii--) + if (drawOrder2[ii] == -1) drawOrder2[ii] = unchanged[--unchangedIndex]; + } + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), drawOrder2); + } + timelines.add(timeline); + } + + /** Event timeline. */ + if (events) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(events->_size); + + for (keyMap = events->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + Event *event; + EventData *eventData = skeletonData->findEvent(Json::getString(keyMap, "name", 0)); + if (!eventData) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Event not found: ", Json::getString(keyMap, "name", 0)); + return NULL; + } + + event = new (__FILE__, __LINE__) Event(Json::getFloat(keyMap, "time", 0), *eventData); + event->_intValue = Json::getInt(keyMap, "int", eventData->_intValue); + event->_floatValue = Json::getFloat(keyMap, "float", eventData->_floatValue); + event->_stringValue = Json::getString(keyMap, "string", eventData->_stringValue.buffer()); + if (!eventData->_audioPath.isEmpty()) { + event->_volume = Json::getFloat(keyMap, "volume", 1); + event->_balance = Json::getFloat(keyMap, "balance", 0); + } + timeline->setFrame(frame, event); + } + timelines.add(timeline); + } + + float duration = 0; + for (size_t i = 0; i < timelines.size(); i++) + duration = MathUtil::max(duration, timelines[i]->getDuration()); + return new (__FILE__, __LINE__) Animation(String(root->_name), timelines, duration); +} + +void SkeletonJson::readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength) { + Json *entry; + size_t i, n, nn, entrySize; + Vector vertices; + + attachment->setWorldVerticesLength(verticesLength); + + entry = Json::getItem(attachmentMap, "vertices"); + entrySize = entry->_size; + vertices.ensureCapacity(entrySize); + vertices.setSize(entrySize, 0); + for (entry = entry->_child, i = 0; entry; entry = entry->_next, ++i) + vertices[i] = entry->_valueFloat; + + if (verticesLength == entrySize) { + if (_scale != 1) { + for (i = 0; i < entrySize; ++i) + vertices[i] *= _scale; + } + + attachment->getVertices().clearAndAddAll(vertices); + return; + } + + Vertices bonesAndWeights; + bonesAndWeights._bones.ensureCapacity(verticesLength * 3); + bonesAndWeights._vertices.ensureCapacity(verticesLength * 3 * 3); + + for (i = 0, n = entrySize; i < n;) { + int boneCount = (int) vertices[i++]; + bonesAndWeights._bones.add(boneCount); + for (nn = i + boneCount * 4; i < nn; i += 4) { + bonesAndWeights._bones.add((int) vertices[i]); + bonesAndWeights._vertices.add(vertices[i + 1] * _scale); + bonesAndWeights._vertices.add(vertices[i + 2] * _scale); + bonesAndWeights._vertices.add(vertices[i + 3]); + } + } + + attachment->getVertices().clearAndAddAll(bonesAndWeights._vertices); + attachment->getBones().clearAndAddAll(bonesAndWeights._bones); +} + +void SkeletonJson::setError(Json *root, const String &value1, const String &value2) { + _error = String(value1).append(value2); + delete root; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SkeletonRenderer.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonRenderer.cpp new file mode 100644 index 000000000..26dbffa95 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SkeletonRenderer.cpp @@ -0,0 +1,246 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +SkeletonRenderer::SkeletonRenderer() : _allocator(4096), _worldVertices(), _quadIndices(), _clipping(), _renderCommands() { + _quadIndices.add(0); + _quadIndices.add(1); + _quadIndices.add(2); + _quadIndices.add(2); + _quadIndices.add(3); + _quadIndices.add(0); +} + +SkeletonRenderer::~SkeletonRenderer() { +} + +static RenderCommand *createRenderCommand(BlockAllocator &allocator, int numVertices, int32_t numIndices, BlendMode blendMode, void *texture) { + RenderCommand *cmd = allocator.allocate(1); + cmd->positions = allocator.allocate(numVertices << 1); + cmd->uvs = allocator.allocate(numVertices << 1); + cmd->colors = allocator.allocate(numVertices); + cmd->darkColors = allocator.allocate(numVertices); + cmd->numVertices = numVertices; + cmd->indices = allocator.allocate(numIndices); + cmd->numIndices = numIndices; + cmd->blendMode = blendMode; + cmd->texture = texture; + cmd->next = nullptr; + return cmd; +} + +static RenderCommand *batchSubCommands(BlockAllocator &allocator, Vector &commands, int first, int last, int numVertices, int numIndices) { + RenderCommand *batched = createRenderCommand(allocator, numVertices, numIndices, commands[first]->blendMode, commands[first]->texture); + float *positions = batched->positions; + float *uvs = batched->uvs; + uint32_t *colors = batched->colors; + uint32_t *darkColors = batched->darkColors; + uint16_t *indices = batched->indices; + int indicesOffset = 0; + for (int i = first; i <= last; i++) { + RenderCommand *cmd = commands[i]; + memcpy(positions, cmd->positions, sizeof(float) * 2 * cmd->numVertices); + memcpy(uvs, cmd->uvs, sizeof(float) * 2 * cmd->numVertices); + memcpy(colors, cmd->colors, sizeof(int32_t) * cmd->numVertices); + memcpy(darkColors, cmd->darkColors, sizeof(int32_t) * cmd->numVertices); + for (int ii = 0; ii < cmd->numIndices; ii++) + indices[ii] = cmd->indices[ii] + indicesOffset; + indicesOffset += cmd->numVertices; + positions += 2 * cmd->numVertices; + uvs += 2 * cmd->numVertices; + colors += cmd->numVertices; + darkColors += cmd->numVertices; + indices += cmd->numIndices; + } + return batched; +} + +static RenderCommand *batchCommands(BlockAllocator &allocator, Vector &commands) { + if (commands.size() == 0) return nullptr; + + RenderCommand *root = nullptr; + RenderCommand *last = nullptr; + + RenderCommand *first = commands[0]; + int startIndex = 0; + int i = 1; + int numVertices = first->numVertices; + int numIndices = first->numIndices; + while (i <= (int) commands.size()) { + RenderCommand *cmd = i < (int) commands.size() ? commands[i] : nullptr; + + if (cmd && cmd->numVertices == 0 && cmd->numIndices == 0) { + i++; + continue; + } + + if (cmd != nullptr && cmd->texture == first->texture && + cmd->blendMode == first->blendMode && + cmd->colors[0] == first->colors[0] && + cmd->darkColors[0] == first->darkColors[0] && + numIndices + cmd->numIndices < 0xffff) { + numVertices += cmd->numVertices; + numIndices += cmd->numIndices; + } else { + RenderCommand *batched = batchSubCommands(allocator, commands, startIndex, i - 1, numVertices, numIndices); + if (!last) { + root = last = batched; + } else { + last->next = batched; + last = batched; + } + if (i == (int) commands.size()) break; + first = commands[i]; + startIndex = i; + numVertices = first->numVertices; + numIndices = first->numIndices; + } + i++; + } + return root; +} + +RenderCommand *SkeletonRenderer::render(Skeleton &skeleton) { + _allocator.compress(); + _renderCommands.clear(); + + SkeletonClipping &clipper = _clipping; + + for (unsigned i = 0; i < skeleton.getSlots().size(); ++i) { + Slot &slot = *skeleton.getDrawOrder()[i]; + Attachment *attachment = slot.getAttachment(); + if (!attachment) { + clipper.clipEnd(slot); + continue; + } + + // Early out if the slot color is 0 or the bone is not active + if ((slot.getColor().a == 0 || !slot.getBone().isActive()) && !attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + clipper.clipEnd(slot); + continue; + } + + Vector *worldVertices = &_worldVertices; + Vector *quadIndices = &_quadIndices; + Vector *vertices = worldVertices; + int32_t verticesCount; + Vector *uvs; + Vector *indices; + int32_t indicesCount; + Color *attachmentColor; + void *texture; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = (RegionAttachment *) attachment; + attachmentColor = ®ionAttachment->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices->setSize(8, 0); + regionAttachment->computeWorldVertices(slot, *worldVertices, 0, 2); + verticesCount = 4; + uvs = ®ionAttachment->getUVs(); + indices = quadIndices; + indicesCount = 6; + texture = regionAttachment->getRegion()->rendererObject; + + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *mesh = (MeshAttachment *) attachment; + attachmentColor = &mesh->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices->buffer(), 0, 2); + verticesCount = (int32_t) (mesh->getWorldVerticesLength() >> 1); + uvs = &mesh->getUVs(); + indices = &mesh->getTriangles(); + indicesCount = (int32_t) indices->size(); + texture = mesh->getRegion()->rendererObject; + + } else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment(); + clipper.clipStart(slot, clip); + continue; + } else + continue; + + uint8_t r = static_cast(skeleton.getColor().r * slot.getColor().r * attachmentColor->r * 255); + uint8_t g = static_cast(skeleton.getColor().g * slot.getColor().g * attachmentColor->g * 255); + uint8_t b = static_cast(skeleton.getColor().b * slot.getColor().b * attachmentColor->b * 255); + uint8_t a = static_cast(skeleton.getColor().a * slot.getColor().a * attachmentColor->a * 255); + uint32_t color = (a << 24) | (r << 16) | (g << 8) | b; + uint32_t darkColor = 0xff000000; + if (slot.hasDarkColor()) { + Color &slotDarkColor = slot.getDarkColor(); + darkColor = 0xff000000 | (static_cast(slotDarkColor.r * 255) << 16) | (static_cast(slotDarkColor.g * 255) << 8) | static_cast(slotDarkColor.b * 255); + } + + if (clipper.isClipping()) { + clipper.clipTriangles(*worldVertices, *indices, *uvs, 2); + vertices = &clipper.getClippedVertices(); + verticesCount = (int32_t) (clipper.getClippedVertices().size() >> 1); + uvs = &clipper.getClippedUVs(); + indices = &clipper.getClippedTriangles(); + indicesCount = (int32_t) (clipper.getClippedTriangles().size()); + } + + RenderCommand *cmd = createRenderCommand(_allocator, verticesCount, indicesCount, slot.getData().getBlendMode(), texture); + _renderCommands.add(cmd); + memcpy(cmd->positions, vertices->buffer(), (verticesCount << 1) * sizeof(float)); + memcpy(cmd->uvs, uvs->buffer(), (verticesCount << 1) * sizeof(float)); + for (int ii = 0; ii < verticesCount; ii++) { + cmd->colors[ii] = color; + cmd->darkColors[ii] = darkColor; + } + memcpy(cmd->indices, indices->buffer(), indices->size() * sizeof(uint16_t)); + clipper.clipEnd(slot); + } + clipper.clipEnd(); + + return batchCommands(_allocator, _renderCommands); +} \ No newline at end of file diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Skin.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Skin.cpp new file mode 100644 index 000000000..782b59491 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Skin.cpp @@ -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. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace spine; + +Skin::AttachmentMap::AttachmentMap() { +} + +static void disposeAttachment(Attachment *attachment) { + if (!attachment) return; + attachment->dereference(); + if (attachment->getRefCount() == 0) delete attachment; +} + +void Skin::AttachmentMap::put(size_t slotIndex, const String &attachmentName, Attachment *attachment) { + if (slotIndex >= _buckets.size()) + _buckets.setSize(slotIndex + 1, Vector()); + Vector &bucket = _buckets[slotIndex]; + int existing = findInBucket(bucket, attachmentName); + attachment->reference(); + if (existing >= 0) { + disposeAttachment(bucket[existing]._attachment); + bucket[existing]._attachment = attachment; + } else { + bucket.add(Entry(slotIndex, attachmentName, attachment)); + } +} + +Attachment *Skin::AttachmentMap::get(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return NULL; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + return existing >= 0 ? _buckets[slotIndex][existing]._attachment : NULL; +} + +void Skin::AttachmentMap::remove(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + if (existing >= 0) { + disposeAttachment(_buckets[slotIndex][existing]._attachment); + _buckets[slotIndex].removeAt(existing); + } +} + +int Skin::AttachmentMap::findInBucket(Vector &bucket, const String &attachmentName) { + for (size_t i = 0; i < bucket.size(); i++) + if (bucket[i]._name == attachmentName) return (int) i; + return -1; +} + +Skin::AttachmentMap::Entries Skin::AttachmentMap::getEntries() { + return Skin::AttachmentMap::Entries(_buckets); +} + +Skin::Skin(const String &name) : _name(name), _attachments(), _color(0.99607843f, 0.61960787f, 0.30980393f, 1) { + assert(_name.length() > 0); +} + +Skin::~Skin() { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry entry = entries.next(); + disposeAttachment(entry._attachment); + } +} + +void Skin::setAttachment(size_t slotIndex, const String &name, Attachment *attachment) { + assert(attachment); + _attachments.put(slotIndex, name, attachment); +} + +Attachment *Skin::getAttachment(size_t slotIndex, const String &name) { + return _attachments.get(slotIndex, name); +} + +void Skin::removeAttachment(size_t slotIndex, const String &name) { + _attachments.remove(slotIndex, name); +} + +void Skin::findNamesForSlot(size_t slotIndex, Vector &names) { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) { + names.add(entry._name); + } + } +} + +void Skin::findAttachmentsForSlot(size_t slotIndex, Vector &attachments) { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) attachments.add(entry._attachment); + } +} + +const String &Skin::getName() { + return _name; +} + +Skin::AttachmentMap::Entries Skin::getAttachments() { + return _attachments.getEntries(); +} + +void Skin::attachAll(Skeleton &skeleton, Skin &oldSkin) { + Vector &slots = skeleton.getSlots(); + Skin::AttachmentMap::Entries entries = oldSkin.getAttachments(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + int slotIndex = (int) entry._slotIndex; + Slot *slot = slots[slotIndex]; + + if (slot->getAttachment() == entry._attachment) { + Attachment *attachment = getAttachment(slotIndex, entry._name); + if (attachment) slot->setAttachment(attachment); + } + } +} + +void Skin::addSkin(Skin *other) { + for (size_t i = 0; i < other->getBones().size(); i++) + if (!_bones.contains(other->getBones()[i])) _bones.add(other->getBones()[i]); + + for (size_t i = 0; i < other->getConstraints().size(); i++) + if (!_constraints.contains(other->getConstraints()[i])) _constraints.add(other->getConstraints()[i]); + + AttachmentMap::Entries entries = other->getAttachments(); + while (entries.hasNext()) { + AttachmentMap::Entry &entry = entries.next(); + setAttachment(entry._slotIndex, entry._name, entry._attachment); + } +} + +void Skin::copySkin(Skin *other) { + for (size_t i = 0; i < other->getBones().size(); i++) + if (!_bones.contains(other->getBones()[i])) _bones.add(other->getBones()[i]); + + for (size_t i = 0; i < other->getConstraints().size(); i++) + if (!_constraints.contains(other->getConstraints()[i])) _constraints.add(other->getConstraints()[i]); + + AttachmentMap::Entries entries = other->getAttachments(); + while (entries.hasNext()) { + AttachmentMap::Entry &entry = entries.next(); + if (entry._attachment->getRTTI().isExactly(MeshAttachment::rtti)) + setAttachment(entry._slotIndex, entry._name, + static_cast(entry._attachment)->newLinkedMesh()); + else + setAttachment(entry._slotIndex, entry._name, entry._attachment->copy()); + } +} + +Vector &Skin::getConstraints() { + return _constraints; +} + +Vector &Skin::getBones() { + return _bones; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Slot.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Slot.cpp new file mode 100644 index 000000000..8501392c2 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Slot.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +Slot::Slot(SlotData &data, Bone &bone) : _data(data), + _bone(bone), + _skeleton(bone.getSkeleton()), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(data.hasDarkColor()), + _attachment(NULL), + _attachmentState(0), + _sequenceIndex(0) { + setToSetupPose(); +} + +void Slot::setToSetupPose() { + _color.set(_data.getColor()); + if (_hasDarkColor) _darkColor.set(_data.getDarkColor()); + + const String &attachmentName = _data.getAttachmentName(); + if (attachmentName.length() > 0) { + _attachment = NULL; + setAttachment(_skeleton.getAttachment(_data.getIndex(), attachmentName)); + } else { + setAttachment(NULL); + } +} + +SlotData &Slot::getData() { + return _data; +} + +Bone &Slot::getBone() { + return _bone; +} + +Skeleton &Slot::getSkeleton() { + return _skeleton; +} + +Color &Slot::getColor() { + return _color; +} + +Color &Slot::getDarkColor() { + return _darkColor; +} + +bool Slot::hasDarkColor() { + return _hasDarkColor; +} + +Attachment *Slot::getAttachment() { + return _attachment; +} + +void Slot::setAttachment(Attachment *inValue) { + if (_attachment == inValue) { + return; + } + + if (!inValue || + !_attachment || + !inValue->getRTTI().instanceOf(VertexAttachment::rtti) || + !_attachment->getRTTI().instanceOf(VertexAttachment::rtti) || + static_cast(inValue)->getTimelineAttachment() != + static_cast(_attachment)->getTimelineAttachment()) { + _deform.clear(); + } + + _attachment = inValue; + _sequenceIndex = -1; +} + +int Slot::getAttachmentState() { + return _attachmentState; +} + +void Slot::setAttachmentState(int state) { + _attachmentState = state; +} + +Vector &Slot::getDeform() { + return _deform; +} + +int Slot::getSequenceIndex() { + return _sequenceIndex; +} + +void Slot::setSequenceIndex(int index) { + _sequenceIndex = index; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SlotData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SlotData.cpp new file mode 100644 index 000000000..3e419e079 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SlotData.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +SlotData::SlotData(int index, const String &name, BoneData &boneData) : _index(index), + _name(name), + _boneData(boneData), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(false), + _attachmentName(), + _blendMode(BlendMode_Normal), + _visible(true) { + assert(_index >= 0); + assert(_name.length() > 0); +} + +int SlotData::getIndex() { + return _index; +} + +const String &SlotData::getName() { + return _name; +} + +BoneData &SlotData::getBoneData() { + return _boneData; +} + +Color &SlotData::getColor() { + return _color; +} + +Color &SlotData::getDarkColor() { + return _darkColor; +} + +bool SlotData::hasDarkColor() { + return _hasDarkColor; +} + +void SlotData::setHasDarkColor(bool inValue) { + _hasDarkColor = inValue; +} + +const String &SlotData::getAttachmentName() { + return _attachmentName; +} + +void SlotData::setAttachmentName(const String &inValue) { + _attachmentName = inValue; +} + +BlendMode SlotData::getBlendMode() { + return _blendMode; +} + +void SlotData::setBlendMode(BlendMode inValue) { + _blendMode = inValue; +} + +bool SlotData::isVisible() { + return _visible; +} + +void SlotData::setVisible(bool inValue) { + this->_visible = inValue; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/SpineObject.cpp b/Projects/SpineCpp/spine-cpp/src/spine/SpineObject.cpp new file mode 100644 index 000000000..1f0bb61a2 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/SpineObject.cpp @@ -0,0 +1,63 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include +#include + +using namespace spine; + +void *SpineObject::operator new(size_t sz) { + return SpineExtension::getInstance()->_calloc(sz, __FILE__, __LINE__); +} + +void *SpineObject::operator new(size_t sz, const char *file, int line) { + return SpineExtension::getInstance()->_calloc(sz, file, line); +} + +void *SpineObject::operator new(size_t sz, void *ptr) { + SP_UNUSED(sz); + return ptr; +} + +void SpineObject::operator delete(void *p, const char *file, int line) { + SpineExtension::free(p, file, line); +} + +void SpineObject::operator delete(void *p, void *mem) { + SP_UNUSED(mem); + SpineExtension::free(p, __FILE__, __LINE__); +} + +void SpineObject::operator delete(void *p) { + SpineExtension::free(p, __FILE__, __LINE__); +} + +SpineObject::~SpineObject() { + SpineExtension::beforeFree(this); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/TextureLoader.cpp b/Projects/SpineCpp/spine-cpp/src/spine/TextureLoader.cpp new file mode 100644 index 000000000..8275b2d2b --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/TextureLoader.cpp @@ -0,0 +1,38 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +namespace spine { + TextureLoader::TextureLoader() { + } + + TextureLoader::~TextureLoader() { + } +}// namespace spine diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Timeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Timeline.cpp new file mode 100644 index 000000000..70d7d5bc7 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Timeline.cpp @@ -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. + *****************************************************************************/ + +#include + +#include +#include + +namespace spine { + RTTI_IMPL_NOPARENT(Timeline) + + Timeline::Timeline(size_t frameCount, size_t frameEntries) + : _propertyIds(), _frames(), _frameEntries(frameEntries) { + _frames.setSize(frameCount * frameEntries, 0); + } + + Timeline::~Timeline() { + } + + Vector &Timeline::getPropertyIds() { + return _propertyIds; + } + + void Timeline::setPropertyIds(PropertyId propertyIds[], size_t propertyIdsCount) { + _propertyIds.clear(); + _propertyIds.ensureCapacity(propertyIdsCount); + for (size_t i = 0; i < propertyIdsCount; i++) { + _propertyIds.add(propertyIds[i]); + } + } + + size_t Timeline::getFrameCount() { + return _frames.size() / _frameEntries; + } + + Vector &Timeline::getFrames() { + return _frames; + } + + size_t Timeline::getFrameEntries() { + return _frameEntries; + } + + float Timeline::getDuration() { + return _frames[_frames.size() - getFrameEntries()]; + } +}// namespace spine diff --git a/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraint.cpp b/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraint.cpp new file mode 100644 index 000000000..56a029a13 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraint.cpp @@ -0,0 +1,350 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraint, Updatable) + +TransformConstraint::TransformConstraint(TransformConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _target(skeleton.findBone( + data.getTarget()->getName())), + _mixRotate( + data.getMixRotate()), + _mixX(data.getMixX()), + _mixY(data.getMixY()), + _mixScaleX( + data.getMixScaleX()), + _mixScaleY( + data.getMixScaleY()), + _mixShearY( + data.getMixShearY()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); ++i) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +void TransformConstraint::update(Physics) { + if (_mixRotate == 0 && _mixX == 0 && _mixY == 0 && _mixScaleX == 0 && _mixScaleY == 0 && _mixShearY == 0) return; + + if (_data.isLocal()) { + if (_data.isRelative()) + applyRelativeLocal(); + else + applyAbsoluteLocal(); + } else { + if (_data.isRelative()) + applyRelativeWorld(); + else + applyAbsoluteWorld(); + } +} + +int TransformConstraint::getOrder() { + return (int) _data.getOrder(); +} + +TransformConstraintData &TransformConstraint::getData() { + return _data; +} + +Vector &TransformConstraint::getBones() { + return _bones; +} + +Bone *TransformConstraint::getTarget() { + return _target; +} + +void TransformConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +float TransformConstraint::getMixRotate() { + return _mixRotate; +} + +void TransformConstraint::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float TransformConstraint::getMixX() { + return _mixX; +} + +void TransformConstraint::setMixX(float inValue) { + _mixX = inValue; +} + +float TransformConstraint::getMixY() { + return _mixY; +} + +void TransformConstraint::setMixY(float inValue) { + _mixY = inValue; +} + +void TransformConstraint::setMixScaleX(float inValue) { + _mixScaleX = inValue; +} + +float TransformConstraint::getMixScaleX() { + return _mixScaleX; +} + +float TransformConstraint::getMixScaleY() { + return _mixScaleY; +} + +void TransformConstraint::setMixScaleY(float inValue) { + _mixScaleY = inValue; +} + +float TransformConstraint::getMixShearY() { + return _mixShearY; +} + +void TransformConstraint::setMixShearY(float inValue) { + _mixShearY = inValue; +} + +void TransformConstraint::applyAbsoluteWorld() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + bool translate = mixX != 0 || mixY != 0; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (mixRotate != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) - MathUtil::atan2(c, a) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + } + + if (translate) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += (tx - bone._worldX) * mixX; + bone._worldY += (ty - bone._worldY) * mixY; + } + + if (mixScaleX > 0) { + float s = MathUtil::sqrt(bone._a * bone._a + bone._c * bone._c); + if (s != 0) s = (s + (MathUtil::sqrt(ta * ta + tc * tc) - s + _data._offsetScaleX) * mixScaleX) / s; + bone._a *= s; + bone._c *= s; + } + + if (mixScaleY > 0) { + float s = MathUtil::sqrt(bone._b * bone._b + bone._d * bone._d); + if (s != 0) s = (s + (MathUtil::sqrt(tb * tb + td * td) - s + _data._offsetScaleY) * mixScaleY) / s; + bone._b *= s; + bone._d *= s; + } + + if (mixShearY > 0) { + float b = bone._b, d = bone._d; + float by = MathUtil::atan2(d, b); + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta) - (by - MathUtil::atan2(bone._c, bone._a)); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r = by + (r + offsetShearY) * mixShearY; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + } + + bone.updateAppliedTransform(); + } +} + +void TransformConstraint::applyRelativeWorld() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + bool translate = mixX != 0 || mixY != 0; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (mixRotate != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + } + + if (translate) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += tx * mixX; + bone._worldY += ty * mixY; + } + + if (mixScaleX != 0) { + float s = (MathUtil::sqrt(ta * ta + tc * tc) - 1 + _data._offsetScaleX) * mixScaleX + 1; + bone._a *= s; + bone._c *= s; + } + if (mixScaleY != 0) { + float s = (MathUtil::sqrt(tb * tb + td * td) - 1 + _data._offsetScaleY) * mixScaleY + 1; + bone._b *= s; + bone._d *= s; + } + + if (mixShearY > 0) { + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + float b = bone._b, d = bone._d; + r = MathUtil::atan2(d, b) + (r - MathUtil::Pi / 2 + offsetShearY) * mixShearY; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + } + + bone.updateAppliedTransform(); + } +} + +void TransformConstraint::applyAbsoluteLocal() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + Bone &target = *_target; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + float rotation = bone._arotation; + if (mixRotate != 0) { + float r = target._arotation - rotation + _data._offsetRotation; + r -= MathUtil::ceil(r / 360 - 0.5) * 360; + rotation += r * mixRotate; + } + + float x = bone._ax, y = bone._ay; + x += (target._ax - x + _data._offsetX) * mixX; + y += (target._ay - y + _data._offsetY) * mixY; + + float scaleX = bone._ascaleX, scaleY = bone._ascaleY; + if (mixScaleX != 0 && scaleX != 0) + scaleX = (scaleX + (target._ascaleX - scaleX + _data._offsetScaleX) * mixScaleX) / scaleX; + if (mixScaleY != 0 && scaleY != 0) + scaleY = (scaleY + (target._ascaleY - scaleY + _data._offsetScaleY) * mixScaleY) / scaleY; + + float shearY = bone._ashearY; + if (mixShearY != 0) { + float r = target._ashearY - shearY + _data._offsetShearY; + r -= MathUtil::ceil(r / 360 - 0.5) * 360; + bone._shearY += r * mixShearY; + } + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +void TransformConstraint::applyRelativeLocal() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + Bone &target = *_target; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + float rotation = bone._arotation + (target._arotation + _data._offsetRotation) * mixRotate; + float x = bone._ax + (target._ax + _data._offsetX) * mixX; + float y = bone._ay + (target._ay + _data._offsetY) * mixY; + float scaleX = bone._ascaleX * (((target._ascaleX - 1 + _data._offsetScaleX) * mixScaleX) + 1); + float scaleY = bone._ascaleY * (((target._ascaleY - 1 + _data._offsetScaleY) * mixScaleY) + 1); + float shearY = bone._ashearY + (target._ashearY + _data._offsetShearY) * mixShearY; + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +bool TransformConstraint::isActive() { + return _active; +} + +void TransformConstraint::setActive(bool inValue) { + _active = inValue; +} + +void TransformConstraint::setToSetupPose() { + TransformConstraintData &data = this->_data; + this->_mixRotate = data._mixRotate; + this->_mixX = data._mixX; + this->_mixY = data._mixY; + this->_mixScaleX = data._mixScaleX; + this->_mixScaleY = data._mixScaleY; + this->_mixShearY = data._mixShearY; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintData.cpp b/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintData.cpp new file mode 100644 index 000000000..5c026096f --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintData.cpp @@ -0,0 +1,180 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraintData, ConstraintData) + +TransformConstraintData::TransformConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _mixRotate(0), + _mixX(0), + _mixY(0), + _mixScaleX(0), + _mixScaleY(0), + _mixShearY(0), + _offsetRotation(0), + _offsetX(0), + _offsetY(0), + _offsetScaleX(0), + _offsetScaleY(0), + _offsetShearY(0), + _relative(false), + _local(false) { +} + +Vector &TransformConstraintData::getBones() { + return _bones; +} + +BoneData *TransformConstraintData::getTarget() { + return _target; +} + +float TransformConstraintData::getMixRotate() { + return _mixRotate; +} + +float TransformConstraintData::getMixX() { + return _mixX; +} + +float TransformConstraintData::getMixY() { + return _mixY; +} + +float TransformConstraintData::getMixScaleX() { + return _mixScaleX; +} + +float TransformConstraintData::getMixScaleY() { + return _mixScaleY; +} + +float TransformConstraintData::getMixShearY() { + return _mixShearY; +} + +float TransformConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +float TransformConstraintData::getOffsetX() { + return _offsetX; +} + +float TransformConstraintData::getOffsetY() { + return _offsetY; +} + +float TransformConstraintData::getOffsetScaleX() { + return _offsetScaleX; +} + +float TransformConstraintData::getOffsetScaleY() { + return _offsetScaleY; +} + +float TransformConstraintData::getOffsetShearY() { + return _offsetShearY; +} + +bool TransformConstraintData::isRelative() { + return _relative; +} + +bool TransformConstraintData::isLocal() { + return _local; +} + +void TransformConstraintData::setTarget(BoneData *target) { + _target = target; +} + +void TransformConstraintData::setMixRotate(float mixRotate) { + _mixRotate = mixRotate; +} + +void TransformConstraintData::setMixX(float mixX) { + _mixX = mixX; +} + +void TransformConstraintData::setMixY(float mixY) { + _mixY = mixY; +} + +void TransformConstraintData::setMixScaleX(float mixScaleX) { + _mixScaleX = mixScaleX; +} + +void TransformConstraintData::setMixScaleY(float mixScaleY) { + _mixScaleY = mixScaleY; +} + +void TransformConstraintData::setMixShearY(float mixShearY) { + _mixShearY = mixShearY; +} + +void TransformConstraintData::setOffsetRotation(float offsetRotation) { + _offsetRotation = offsetRotation; +} + +void TransformConstraintData::setOffsetX(float offsetX) { + _offsetX = offsetX; +} + +void TransformConstraintData::setOffsetY(float offsetY) { + _offsetY = offsetY; +} + +void TransformConstraintData::setOffsetScaleX(float offsetScaleX) { + _offsetScaleX = offsetScaleX; +} + +void TransformConstraintData::setOffsetScaleY(float offsetScaleY) { + _offsetScaleY = offsetScaleY; +} + +void TransformConstraintData::setOffsetShearY(float offsetShearY) { + _offsetShearY = offsetShearY; +} + +void TransformConstraintData::setRelative(bool isRelative) { + _relative = isRelative; +} + +void TransformConstraintData::setLocal(bool isLocal) { + _local = isLocal; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintTimeline.cpp new file mode 100644 index 000000000..eee2e6cea --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/TransformConstraintTimeline.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraintTimeline, CurveTimeline) + +TransformConstraintTimeline::TransformConstraintTimeline(size_t frameCount, size_t bezierCount, + int transformConstraintIndex) : CurveTimeline(frameCount, + TransformConstraintTimeline::ENTRIES, + bezierCount), + _constraintIndex( + transformConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_TransformConstraint << 32) | transformConstraintIndex}; + setPropertyIds(ids, 1); +} + +void TransformConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + TransformConstraint *constraintP = skeleton._transformConstraints[_constraintIndex]; + TransformConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + TransformConstraintData &data = constraint._data; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mixRotate = data._mixRotate; + constraint._mixX = data._mixX; + constraint._mixY = data._mixY; + constraint._mixScaleX = data._mixScaleX; + constraint._mixScaleY = data._mixScaleY; + constraint._mixShearY = data._mixShearY; + return; + case MixBlend_First: + constraint._mixRotate += (data._mixRotate - constraint._mixRotate) * alpha; + constraint._mixX += (data._mixX - constraint._mixX) * alpha; + constraint._mixY += (data._mixY - constraint._mixY) * alpha; + constraint._mixScaleX += (data._mixScaleX - constraint._mixScaleX) * alpha; + constraint._mixScaleY += (data._mixScaleY - constraint._mixScaleY) * alpha; + constraint._mixShearY += (data._mixShearY - constraint._mixShearY) * alpha; + return; + default: + return; + } + } + + float rotate, x, y, scaleX, scaleY, shearY; + int i = Animation::search(_frames, time, TransformConstraintTimeline::ENTRIES); + int curveType = (int) _curves[i / TransformConstraintTimeline::ENTRIES]; + switch (curveType) { + case TransformConstraintTimeline::LINEAR: { + float before = _frames[i]; + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + scaleX = _frames[i + SCALEX]; + scaleY = _frames[i + SCALEY]; + shearY = _frames[i + SHEARY]; + float t = (time - before) / (_frames[i + ENTRIES] - before); + rotate += (_frames[i + ENTRIES + ROTATE] - rotate) * t; + x += (_frames[i + ENTRIES + X] - x) * t; + y += (_frames[i + ENTRIES + Y] - y) * t; + scaleX += (_frames[i + ENTRIES + SCALEX] - scaleX) * t; + scaleY += (_frames[i + ENTRIES + SCALEY] - scaleY) * t; + shearY += (_frames[i + ENTRIES + SHEARY] - shearY) * t; + break; + } + case TransformConstraintTimeline::STEPPED: { + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + scaleX = _frames[i + SCALEX]; + scaleY = _frames[i + SCALEY]; + shearY = _frames[i + SHEARY]; + break; + } + default: { + rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER); + x = getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER); + y = getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER); + scaleX = getBezierValue(time, i, SCALEX, curveType + BEZIER_SIZE * 3 - BEZIER); + scaleY = getBezierValue(time, i, SCALEY, curveType + BEZIER_SIZE * 4 - BEZIER); + shearY = getBezierValue(time, i, SHEARY, curveType + BEZIER_SIZE * 5 - BEZIER); + } + } + + if (blend == MixBlend_Setup) { + constraint._mixRotate = data._mixRotate + (rotate - data._mixRotate) * alpha; + constraint._mixX = data._mixX + (x - data._mixX) * alpha; + constraint._mixY = data._mixY + (y - data._mixY) * alpha; + constraint._mixScaleX = data._mixScaleX + (scaleX - data._mixScaleX) * alpha; + constraint._mixScaleY = data._mixScaleY + (scaleY - data._mixScaleY) * alpha; + constraint._mixShearY = data._mixShearY + (shearY - data._mixShearY) * alpha; + } else { + constraint._mixRotate += (rotate - constraint._mixRotate) * alpha; + constraint._mixX += (x - constraint._mixX) * alpha; + constraint._mixY += (y - constraint._mixY) * alpha; + constraint._mixScaleX += (scaleX - constraint._mixScaleX) * alpha; + constraint._mixScaleY += (scaleY - constraint._mixScaleY) * alpha; + constraint._mixShearY += (shearY - constraint._mixShearY) * alpha; + } +} + +void TransformConstraintTimeline::setFrame(size_t frame, float time, float mixRotate, float mixX, float mixY, + float mixScaleX, float mixScaleY, float mixShearY) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + ROTATE] = mixRotate; + _frames[frame + X] = mixX; + _frames[frame + Y] = mixY; + _frames[frame + SCALEX] = mixScaleX; + _frames[frame + SCALEY] = mixScaleY; + _frames[frame + SHEARY] = mixShearY; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/TranslateTimeline.cpp b/Projects/SpineCpp/spine-cpp/src/spine/TranslateTimeline.cpp new file mode 100644 index 000000000..559874706 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/TranslateTimeline.cpp @@ -0,0 +1,162 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TranslateTimeline, CurveTimeline2) + +TranslateTimeline::TranslateTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_X << 32) | boneIndex, + ((PropertyId) Property_Y << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +TranslateTimeline::~TranslateTimeline() { +} + +void TranslateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_x = bone->_data._x; + bone->_y = bone->_data._y; + return; + case MixBlend_First: + bone->_x += (bone->_data._x - bone->_x) * alpha; + bone->_y += (bone->_data._y - bone->_y) * alpha; + default: { + } + } + return; + } + + float x = 0, y = 0; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline::BEZIER_SIZE - CurveTimeline::BEZIER); + } + } + + switch (blend) { + case MixBlend_Setup: + bone->_x = bone->_data._x + x * alpha; + bone->_y = bone->_data._y + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone->_x += (bone->_data._x + x - bone->_x) * alpha; + bone->_y += (bone->_data._y + y - bone->_y) * alpha; + break; + case MixBlend_Add: + bone->_x += x * alpha; + bone->_y += y * alpha; + } +} + +RTTI_IMPL(TranslateXTimeline, CurveTimeline1) + +TranslateXTimeline::TranslateXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1( + frameCount, bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_X << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +TranslateXTimeline::~TranslateXTimeline() { +} + +void TranslateXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_x = getRelativeValue(time, alpha, blend, bone->_x, bone->_data._x); +} + +RTTI_IMPL(TranslateYTimeline, CurveTimeline1) + +TranslateYTimeline::TranslateYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1( + frameCount, bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Y << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +TranslateYTimeline::~TranslateYTimeline() { +} + +void TranslateYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_y = getRelativeValue(time, alpha, blend, bone->_y, bone->_data._y); +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Triangulator.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Triangulator.cpp new file mode 100644 index 000000000..fdbdb04cd --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Triangulator.cpp @@ -0,0 +1,285 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +Triangulator::~Triangulator() { + ContainerUtil::cleanUpVectorOfPointers(_convexPolygons); + ContainerUtil::cleanUpVectorOfPointers(_convexPolygonsIndices); +} + +Vector &Triangulator::triangulate(Vector &vertices) { + size_t vertexCount = vertices.size() >> 1; + + Vector &indices = _indices; + indices.clear(); + indices.ensureCapacity(vertexCount); + indices.setSize(vertexCount, 0); + for (int i = 0; i < (int) vertexCount; ++i) { + indices[i] = i; + } + + Vector &isConcaveArray = _isConcaveArray; + isConcaveArray.ensureCapacity(vertexCount); + isConcaveArray.setSize(vertexCount, 0); + for (int i = 0, n = (int) vertexCount; i < n; ++i) { + isConcaveArray[i] = isConcave(i, (int) vertexCount, vertices, indices); + } + + Vector &triangles = _triangles; + triangles.clear(); + triangles.ensureCapacity(MathUtil::max((int) 0, (int) vertexCount - 2) << 2); + + while (vertexCount > 3) { + // Find ear tip. + size_t previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) { + if (!isConcaveArray[i]) { + int p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1; + float p1x = vertices[p1], p1y = vertices[p1 + 1]; + float p2x = vertices[p2], p2y = vertices[p2 + 1]; + float p3x = vertices[p3], p3y = vertices[p3 + 1]; + for (size_t ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { + if (!isConcaveArray[ii]) continue; + + int v = indices[ii] << 1; + float &vx = vertices[v], vy = vertices[v + 1]; + if (positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { + if (positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { + if (positiveArea(p2x, p2y, p3x, p3y, vx, vy)) { + goto break_outer;// break outer; + } + } + } + } + break; + } + break_outer: + + if (next == 0) { + do { + if (!isConcaveArray[i]) break; + i--; + } while (i > 0); + break; + } + + previous = i; + i = next; + next = (next + 1) % vertexCount; + } + + // Cut ear tip. + triangles.add(indices[(vertexCount + i - 1) % vertexCount]); + triangles.add(indices[i]); + triangles.add(indices[(i + 1) % vertexCount]); + indices.removeAt(i); + isConcaveArray.removeAt(i); + vertexCount--; + + int previousIndex = (int) ((vertexCount + i - 1) % vertexCount); + int nextIndex = (int) (i == vertexCount ? 0 : i); + isConcaveArray[previousIndex] = isConcave(previousIndex, (int) vertexCount, vertices, indices); + isConcaveArray[nextIndex] = isConcave(nextIndex, (int) vertexCount, vertices, indices); + } + + if (vertexCount == 3) { + triangles.add(indices[2]); + triangles.add(indices[0]); + triangles.add(indices[1]); + } + + return triangles; +} + +Vector *> &Triangulator::decompose(Vector &vertices, Vector &triangles) { + Vector *> &convexPolygons = _convexPolygons; + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) + _polygonPool.free(convexPolygons[i]); + convexPolygons.clear(); + + Vector *> &convexPolygonsIndices = _convexPolygonsIndices; + for (size_t i = 0, n = convexPolygonsIndices.size(); i < n; ++i) + _polygonIndicesPool.free(convexPolygonsIndices[i]); + convexPolygonsIndices.clear(); + + Vector *polygonIndices = _polygonIndicesPool.obtain(); + polygonIndices->clear(); + + Vector *polygon = _polygonPool.obtain(); + polygon->clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastwinding = 0; + for (size_t i = 0, n = triangles.size(); i < n; i += 3) { + int t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[i + 2] << 1; + float x1 = vertices[t1], y1 = vertices[t1 + 1]; + float x2 = vertices[t2], y2 = vertices[t2 + 1]; + float x3 = vertices[t3], y3 = vertices[t3 + 1]; + + // If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). + bool merged = false; + if (fanBaseIndex == t1) { + size_t o = polygon->size() - 4; + Vector &p = *polygon; + int winding1 = winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3); + int winding2 = winding(x3, y3, p[0], p[1], p[2], p[3]); + if (winding1 == lastwinding && winding2 == lastwinding) { + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(t3); + merged = true; + } + } + + // Otherwise make this triangle the new base. + if (!merged) { + if (polygon->size() > 0) { + convexPolygons.add(polygon); + convexPolygonsIndices.add(polygonIndices); + } else { + _polygonPool.free(polygon); + _polygonIndicesPool.free(polygonIndices); + } + + polygon = _polygonPool.obtain(); + polygon->clear(); + polygon->add(x1); + polygon->add(y1); + polygon->add(x2); + polygon->add(y2); + polygon->add(x3); + polygon->add(y3); + polygonIndices = _polygonIndicesPool.obtain(); + polygonIndices->clear(); + polygonIndices->add(t1); + polygonIndices->add(t2); + polygonIndices->add(t3); + lastwinding = winding(x1, y1, x2, y2, x3, y3); + fanBaseIndex = t1; + } + } + + if (polygon->size() > 0) { + convexPolygons.add(polygon); + convexPolygonsIndices.add(polygonIndices); + } + + // Go through the list of polygons and try to merge the remaining triangles with the found triangle fans. + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) { + polygonIndices = convexPolygonsIndices[i]; + + if (polygonIndices->size() == 0) continue; + int firstIndex = (*polygonIndices)[0]; + int lastIndex = (*polygonIndices)[polygonIndices->size() - 1]; + + polygon = convexPolygons[i]; + size_t o = polygon->size() - 4; + Vector &p = *polygon; + float prevPrevX = p[o], prevPrevY = p[o + 1]; + float prevX = p[o + 2], prevY = p[o + 3]; + float firstX = p[0], firstY = p[1]; + float secondX = p[2], secondY = p[3]; + int winding0 = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (size_t ii = 0; ii < n; ++ii) { + if (ii == i) continue; + + Vector *otherIndicesP = convexPolygonsIndices[ii]; + Vector &otherIndices = *otherIndicesP; + + if (otherIndices.size() != 3) continue; + + int otherFirstIndex = otherIndices[0]; + int otherSecondIndex = otherIndices[1]; + int otherLastIndex = otherIndices[2]; + + Vector *otherPolyP = convexPolygons[ii]; + Vector &otherPoly = *otherPolyP; + + float x3 = otherPoly[otherPoly.size() - 2], y3 = otherPoly[otherPoly.size() - 1]; + + if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; + + int winding1 = winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); + int winding2 = winding(x3, y3, firstX, firstY, secondX, secondY); + if (winding1 == winding0 && winding2 == winding0) { + otherPoly.clear(); + otherIndices.clear(); + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(otherLastIndex); + prevPrevX = prevX; + prevPrevY = prevY; + prevX = x3; + prevY = y3; + ii = 0; + } + } + } + + // Remove empty polygons that resulted from the merge step above. + for (int i = (int) convexPolygons.size() - 1; i >= 0; --i) { + polygon = convexPolygons[i]; + if (polygon->size() == 0) { + convexPolygons.removeAt(i); + _polygonPool.free(polygon); + polygonIndices = convexPolygonsIndices[i]; + convexPolygonsIndices.removeAt(i); + _polygonIndicesPool.free(polygonIndices); + } + } + + return convexPolygons; +} + +bool Triangulator::isConcave(int index, int vertexCount, Vector &vertices, Vector &indices) { + int previous = indices[(vertexCount + index - 1) % vertexCount] << 1; + int current = indices[index] << 1; + int next = indices[(index + 1) % vertexCount] << 1; + + return !positiveArea(vertices[previous], vertices[previous + 1], + vertices[current], vertices[current + 1], + vertices[next], vertices[next + 1]); +} + +bool Triangulator::positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; +} + +int Triangulator::winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { + float px = p2x - p1x, py = p2y - p1y; + return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1; +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/Updatable.cpp b/Projects/SpineCpp/spine-cpp/src/spine/Updatable.cpp new file mode 100644 index 000000000..9339118e2 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/Updatable.cpp @@ -0,0 +1,40 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(Updatable) + +Updatable::Updatable() { +} + +Updatable::~Updatable() { +} diff --git a/Projects/SpineCpp/spine-cpp/src/spine/VertexAttachment.cpp b/Projects/SpineCpp/spine-cpp/src/spine/VertexAttachment.cpp new file mode 100644 index 000000000..c888608c8 --- /dev/null +++ b/Projects/SpineCpp/spine-cpp/src/spine/VertexAttachment.cpp @@ -0,0 +1,167 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#include + +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(VertexAttachment, Attachment) + +VertexAttachment::VertexAttachment(const String &name) : Attachment(name), _worldVerticesLength(0), + _timelineAttachment(this), _id(getNextID()) { +} + +VertexAttachment::~VertexAttachment() { +} + +void VertexAttachment::computeWorldVertices(Slot &slot, Vector &worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, float *worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, Vector &worldVertices, + size_t offset, size_t stride) { + computeWorldVertices(slot, start, count, worldVertices.buffer(), offset, stride); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride) { + count = offset + (count >> 1) * stride; + Skeleton &skeleton = slot._bone._skeleton; + Vector *deformArray = &slot.getDeform(); + Vector *vertices = &_vertices; + Vector &bones = _bones; + if (bones.size() == 0) { + if (deformArray->size() > 0) vertices = deformArray; + + Bone &bone = slot._bone; + float x = bone._worldX; + float y = bone._worldY; + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + for (size_t vv = start, w = offset; w < count; vv += 2, w += stride) { + float vx = (*vertices)[vv]; + float vy = (*vertices)[vv + 1]; + worldVertices[w] = vx * a + vy * b + x; + worldVertices[w + 1] = vx * c + vy * d + y; + } + return; + } + + int v = 0, skip = 0; + for (size_t i = 0; i < start; i += 2) { + int n = (int) bones[v]; + v += n + 1; + skip += n; + } + + Vector &skeletonBones = skeleton.getBones(); + if (deformArray->size() == 0) { + for (size_t w = offset, b = skip * 3; w < count; w += stride) { + float wx = 0, wy = 0; + int n = (int) bones[v++]; + n += v; + for (; v < n; v++, b += 3) { + Bone *boneP = skeletonBones[bones[v]]; + Bone &bone = *boneP; + float vx = (*vertices)[b]; + float vy = (*vertices)[b + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } else { + for (size_t w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { + float wx = 0, wy = 0; + int n = (int) bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) { + Bone *boneP = skeletonBones[bones[v]]; + Bone &bone = *boneP; + float vx = (*vertices)[b] + (*deformArray)[f]; + float vy = (*vertices)[b + 1] + (*deformArray)[f + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } +} + +int VertexAttachment::getId() { + return _id; +} + +Vector &VertexAttachment::getBones() { + return _bones; +} + +Vector &VertexAttachment::getVertices() { + return _vertices; +} + +size_t VertexAttachment::getWorldVerticesLength() { + return _worldVerticesLength; +} + +void VertexAttachment::setWorldVerticesLength(size_t inValue) { + _worldVerticesLength = inValue; +} + +Attachment *VertexAttachment::getTimelineAttachment() { + return _timelineAttachment; +} + +void VertexAttachment::setTimelineAttachment(Attachment *attachment) { + _timelineAttachment = attachment; +} + +int VertexAttachment::getNextID() { + static int nextID = 0; + return nextID++; +} + +void VertexAttachment::copyTo(VertexAttachment *other) { + other->_bones.clearAndAddAll(this->_bones); + other->_vertices.clearAndAddAll(this->_vertices); + other->_worldVerticesLength = this->_worldVerticesLength; + other->_timelineAttachment = this->_timelineAttachment; +} diff --git a/SkylichtConfig.cmake b/SkylichtConfig.cmake index 1ee0cbdc3..93e33c023 100644 --- a/SkylichtConfig.cmake +++ b/SkylichtConfig.cmake @@ -1,5 +1,6 @@ option(BUILD_IMGUI "Build Ocornut Imgui" ON) option(BUILD_FREETYPE "Build freetype font library" ON) +option(BUILD_SPINE_RUNTIMES "Build spine runtimes library" ON) option(BUILD_SKYLICHT_AUDIO "Build with audio engine" ON) option(BUILD_SKYLICHT_COMPONENTS "Build with components" ON) option(BUILD_SKYLICHT_LIGHMAPPER "Build with lightmapper" ON) From 587bc0725712f401dd93fe6e66cd738e47b38567 Mon Sep 17 00:00:00 2001 From: "duc.phamhong" Date: Thu, 3 Oct 2024 12:24:33 +0700 Subject: [PATCH 2/5] Add example load spine2D --- Assets/BuildAsset.py | 3 +- Assets/SampleSpine2D/license.txt | 8 + Assets/SampleSpine2D/spineboy-pma.atlas | 95 + Assets/SampleSpine2D/spineboy-pma.png | Bin 0 -> 244861 bytes Assets/SampleSpine2D/spineboy-pro.json | 8723 +++++++++++++++++ CMakeProjects.cmake | 5 + .../SpineRuntimes/CSkeletonDrawable.cpp | 46 +- .../SpineRuntimes/CSkeletonDrawable.h | 29 +- .../Skylicht/SpineRuntimes/CTextureLoader.cpp | 39 +- .../Skylicht/SpineRuntimes/CTextureLoader.h | 17 +- Projects/SpineCpp/CMakeLists.txt | 6 + Samples/Spine2D/CMakeLists.txt | 344 + Samples/Spine2D/Source/CViewDemo.cpp | 115 + Samples/Spine2D/Source/CViewDemo.h | 23 + Samples/Spine2D/Source/CViewInit.cpp | 260 + Samples/Spine2D/Source/CViewInit.h | 49 + Samples/Spine2D/Source/SampleSpine2D.cpp | 83 + Samples/Spine2D/Source/SampleSpine2D.h | 28 + Samples/Spine2D/Source/Version.h | 4 + Scripts/CMakeLists.txt | 12 + 20 files changed, 9840 insertions(+), 49 deletions(-) create mode 100644 Assets/SampleSpine2D/license.txt create mode 100644 Assets/SampleSpine2D/spineboy-pma.atlas create mode 100644 Assets/SampleSpine2D/spineboy-pma.png create mode 100644 Assets/SampleSpine2D/spineboy-pro.json create mode 100644 Samples/Spine2D/CMakeLists.txt create mode 100644 Samples/Spine2D/Source/CViewDemo.cpp create mode 100644 Samples/Spine2D/Source/CViewDemo.h create mode 100644 Samples/Spine2D/Source/CViewInit.cpp create mode 100644 Samples/Spine2D/Source/CViewInit.h create mode 100644 Samples/Spine2D/Source/SampleSpine2D.cpp create mode 100644 Samples/Spine2D/Source/SampleSpine2D.h create mode 100644 Samples/Spine2D/Source/Version.h diff --git a/Assets/BuildAsset.py b/Assets/BuildAsset.py index ca7fb74b1..625e5d94e 100644 --- a/Assets/BuildAsset.py +++ b/Assets/BuildAsset.py @@ -8,7 +8,8 @@ "png", "jpg", "smesh", "sanim", "mp3", "wav", - "font"] + "font", + "atlas","json"] compressResourceModel = ["dae", "obj", "mtl", "fbx"] compressTextureDDSExt = ["dds"] compressTexturePVRExt = ["pvr"] diff --git a/Assets/SampleSpine2D/license.txt b/Assets/SampleSpine2D/license.txt new file mode 100644 index 000000000..ba9fe3d72 --- /dev/null +++ b/Assets/SampleSpine2D/license.txt @@ -0,0 +1,8 @@ +Copyright (c) 2013, Esoteric Software LLC + +The images in this project may be redistributed as long as they are accompanied +by this license file. The images may not be used for commercial use of any +kind. + +The project file is released into the public domain. It may be used as the basis +for derivative work. \ No newline at end of file diff --git a/Assets/SampleSpine2D/spineboy-pma.atlas b/Assets/SampleSpine2D/spineboy-pma.atlas new file mode 100644 index 000000000..ad3d77b11 --- /dev/null +++ b/Assets/SampleSpine2D/spineboy-pma.atlas @@ -0,0 +1,95 @@ +spineboy-pma.png + size: 1024, 256 + filter: Linear, Linear + pma: true + scale: 0.5 +crosshair + bounds: 352, 7, 45, 45 +eye-indifferent + bounds: 862, 105, 47, 45 +eye-surprised + bounds: 505, 79, 47, 45 +front-bracer + bounds: 826, 66, 29, 40 +front-fist-closed + bounds: 786, 65, 38, 41 +front-fist-open + bounds: 710, 51, 43, 44 + rotate: 90 +front-foot + bounds: 210, 6, 63, 35 +front-shin + bounds: 665, 128, 41, 92 + rotate: 90 +front-thigh + bounds: 2, 2, 23, 56 + rotate: 90 +front-upper-arm + bounds: 250, 205, 23, 49 +goggles + bounds: 665, 171, 131, 83 +gun + bounds: 798, 152, 105, 102 +head + bounds: 2, 27, 136, 149 +hoverboard-board + bounds: 2, 178, 246, 76 +hoverboard-thruster + bounds: 722, 96, 30, 32 + rotate: 90 +hoverglow-small + bounds: 275, 81, 137, 38 +mouth-grind + bounds: 614, 97, 47, 30 +mouth-oooo + bounds: 612, 65, 47, 30 +mouth-smile + bounds: 661, 64, 47, 30 +muzzle-glow + bounds: 382, 54, 25, 25 +muzzle-ring + bounds: 275, 54, 25, 105 + rotate: 90 +muzzle01 + bounds: 911, 95, 67, 40 + rotate: 90 +muzzle02 + bounds: 792, 108, 68, 42 +muzzle03 + bounds: 956, 171, 83, 53 + rotate: 90 +muzzle04 + bounds: 275, 7, 75, 45 +muzzle05 + bounds: 140, 3, 68, 38 +neck + bounds: 250, 182, 18, 21 +portal-bg + bounds: 140, 43, 133, 133 +portal-flare1 + bounds: 554, 65, 56, 30 +portal-flare2 + bounds: 759, 112, 57, 31 + rotate: 90 +portal-flare3 + bounds: 554, 97, 58, 30 +portal-shade + bounds: 275, 121, 133, 133 +portal-streaks1 + bounds: 410, 126, 126, 128 +portal-streaks2 + bounds: 538, 129, 125, 125 +rear-bracer + bounds: 857, 67, 28, 36 +rear-foot + bounds: 663, 96, 57, 30 +rear-shin + bounds: 414, 86, 38, 89 + rotate: 90 +rear-thigh + bounds: 756, 63, 28, 47 +rear-upper-arm + bounds: 60, 5, 20, 44 + rotate: 90 +torso + bounds: 905, 164, 49, 90 diff --git a/Assets/SampleSpine2D/spineboy-pma.png b/Assets/SampleSpine2D/spineboy-pma.png new file mode 100644 index 0000000000000000000000000000000000000000..711fd836ee66dd79f88e29ade17d2c78a64b4a46 GIT binary patch literal 244861 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 + + delete m_animationState; + delete m_skeleton; + } - } + void CSkeletonDrawable::update(float delta, spine::Physics physics) + { + m_animationState->update(delta); + m_animationState->apply(*m_skeleton); + m_skeleton->update(delta); + m_skeleton->updateWorldTransform(physics); + } - CSkeletonDrawable::~CSkeletonDrawable() - { + void CSkeletonDrawable::render() + { - } } } \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h b/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h index e427a2e19..6ab74f516 100644 --- a/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h +++ b/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h @@ -25,17 +25,26 @@ This file is part of the "Skylicht Engine". #pragma once #include +#include "Graphics2D/GUI/CGUIElement.h" -namespace Skylicht +namespace spine { - namespace Spine + class CSkeletonDrawable { - class CSkeletonDrawable - { - public: - CSkeletonDrawable(); - - virtual ~CSkeletonDrawable(); - }; - } + protected: + spine::Skeleton* m_skeleton; + spine::AnimationState* m_animationState; + + bool m_usePremultipliedAlpha; + bool m_ownsAnimationStateData; + + public: + CSkeletonDrawable(spine::SkeletonData* skeletonData, spine::AnimationStateData* animationStateData = NULL); + + virtual ~CSkeletonDrawable(); + + void update(float delta, spine::Physics physics); + + void render(); + }; } \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp b/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp index 43f99dc1d..c94252d9f 100644 --- a/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp +++ b/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp @@ -27,27 +27,26 @@ This file is part of the "Skylicht Engine". #include "TextureManager/CTextureManager.h" -namespace Skylicht +using namespace Skylicht; + +namespace spine { - namespace Spine + CTextureLoader::CTextureLoader() + { + + } + + void CTextureLoader::load(spine::AtlasPage& page, const spine::String& path) + { + ITexture* texture = CTextureManager::getInstance()->getTexture(path.buffer()); + if (!texture) + return; + page.texture = texture; + } + + void CTextureLoader::unload(void* texture) { - CTextureLoader::CTextureLoader() - { - - } - - void CTextureLoader::load(spine::AtlasPage& page, const spine::String& path) - { - ITexture* texture = CTextureManager::getInstance()->getTexture(path.buffer()); - if (!texture) - return; - page.texture = texture; - } - - void CTextureLoader::unload(void* texture) - { - ITexture* t = (ITexture*)texture; - CTextureManager::getInstance()->removeTexture(t); - } + ITexture* t = (ITexture*)texture; + CTextureManager::getInstance()->removeTexture(t); } } \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CTextureLoader.h b/Projects/Skylicht/SpineRuntimes/CTextureLoader.h index 407299d60..c88ba8f8e 100644 --- a/Projects/Skylicht/SpineRuntimes/CTextureLoader.h +++ b/Projects/Skylicht/SpineRuntimes/CTextureLoader.h @@ -26,18 +26,15 @@ This file is part of the "Skylicht Engine". #include -namespace Skylicht +namespace spine { - namespace Spine + class CTextureLoader : public spine::TextureLoader { - class CTextureLoader : public spine::TextureLoader - { - public: - CTextureLoader(); + public: + CTextureLoader(); - void load(spine::AtlasPage& page, const spine::String& path); + void load(spine::AtlasPage& page, const spine::String& path); - void unload(void* texture); - }; - } + void unload(void* texture); + }; } \ No newline at end of file diff --git a/Projects/SpineCpp/CMakeLists.txt b/Projects/SpineCpp/CMakeLists.txt index 6dc5cb54e..ed0f00e1a 100644 --- a/Projects/SpineCpp/CMakeLists.txt +++ b/Projects/SpineCpp/CMakeLists.txt @@ -6,5 +6,11 @@ include_directories(include) file(GLOB INCLUDES "spine-cpp/include/**/*.h") file(GLOB SOURCES "spine-cpp/src/**/*.cpp") +if (BUILD_SHARED_LIBS) +if (BUILD_LINUX OR BUILD_MACOS) +add_compile_options(-fpic) +endif() +endif() + add_library(spine-cpp STATIC ${SOURCES} ${INCLUDES}) target_include_directories(spine-cpp PUBLIC spine-cpp/include) \ No newline at end of file diff --git a/Samples/Spine2D/CMakeLists.txt b/Samples/Spine2D/CMakeLists.txt new file mode 100644 index 000000000..7a97642f5 --- /dev/null +++ b/Samples/Spine2D/CMakeLists.txt @@ -0,0 +1,344 @@ +include_directories( + ${SKYLICHT_ENGINE_SOURCE_DIR}/Samples/Spine2D/Source + ${SKYLICHT_ENGINE_SOURCE_DIR}/Samples/SampleFW + ${SKYLICHT_ENGINE_PROJECT_DIR}/Main + ${SKYLICHT_ENGINE_PROJECT_DIR}/Irrlicht/Include + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/System + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Engine + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Components + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Collision + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Physics + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Client + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Lightmapper + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Audio + ${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdParty/freetype2/include + ${SKYLICHT_ENGINE_PROJECT_DIR}/Bullet3/src +) + +if (BUILD_IMGUI) + include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Imgui) +endif() + +if (BUILD_SPINE_RUNTIMES) + include_directories( + ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp/spine-cpp/include + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes + ) +endif() + +set(template_path ${SKYLICHT_ENGINE_PROJECT_DIR}/Main) + +file(GLOB_RECURSE application_source + ./Source/**.cpp + ./Source/**.c + ./Source/**.h + ../SampleFW/**.cpp + ../SampleFW/**.h) + +setup_project_group("${application_source}" ${CMAKE_CURRENT_SOURCE_DIR}) + +list (APPEND application_source ${template_path}/pch.cpp) +list (APPEND application_source ${template_path}/pch.h) + +if (BUILD_ANDROID) + file(GLOB_RECURSE platform_android_source + ${template_path}/Platforms/Android/**.cpp + ${template_path}/Platforms/Android/**.c + ${template_path}/Platforms/Android/**.h) + + list (APPEND application_source ${platform_android_source}) +endif() + +if (BUILD_WINDOWS_STORE) + file(MAKE_DIRECTORY ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D) + file(MAKE_DIRECTORY ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D/Assets) + + file(GLOB uwp_assets + ${template_path}/Platforms/UWP/Assets/**.* + ${SKYLICHT_ENGINE_BIN_DIR}/**.zip) + + foreach(asset_file ${uwp_assets}) + file(COPY "${asset_file}" DESTINATION ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D/Assets) + endforeach() + + file(COPY ${template_path}/Platforms/UWP/Package.appxmanifest + DESTINATION + ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D) + + file(COPY ${template_path}/Platforms/UWP/TemporaryKey.pfx + DESTINATION + ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D) + + file(GLOB_RECURSE platform_uwp_source + ${template_path}/Platforms/UWP/**.cpp + ${template_path}/Platforms/UWP/**.c + ${template_path}/Platforms/UWP/**.h) + + # copy resource to generate prj folder + file(GLOB_RECURSE platform_uwp_asset ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D/Assets/**.*) + add_source_group("${platform_uwp_asset}" "Assets") + + list (APPEND application_source ${platform_uwp_source}) + + set_property(SOURCE ${platform_uwp_asset} PROPERTY VS_DEPLOYMENT_CONTENT 1) +endif() + +if (MSVC OR CYGWIN OR MINGW) + include_directories(${template_path}/Platforms/Win32) + + file(GLOB_RECURSE platform_win32_source + ${template_path}/Platforms/Win32/**.cpp + ${template_path}/Platforms/Win32/**.c + ${template_path}/Platforms/Win32/**.h) + + list (APPEND application_source ${platform_win32_source}) +endif() + +if (BUILD_MACOS) + # Angle API + include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Angle/include) + include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Irrlicht/Source/Angle) + include_directories(${template_path}/Platforms/MacOS) + + file(GLOB_RECURSE platform_mac_source + ${template_path}/Platforms/MacOS/**.cpp + ${template_path}/Platforms/MacOS/**.c + ${template_path}/Platforms/MacOS/**.h + ${template_path}/Platforms/MacOS/**.m + ${template_path}/Platforms/MacOS/**.mm) + + list (APPEND application_source ${platform_mac_source}) +endif() + +if (BUILD_IOS) + # Angle API + include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Angle/include) + include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Irrlicht/Source/Angle) + include_directories(${template_path}/Platforms/IOS) + + file(GLOB_RECURSE platform_ios_source + ${template_path}/Platforms/IOS/**.cpp + ${template_path}/Platforms/IOS/**.c + ${template_path}/Platforms/IOS/**.h + ${template_path}/Platforms/IOS/**.m + ${template_path}/Platforms/IOS/**.mm) + + file(GLOB_RECURSE platform_ios_resources ${template_path}/Platforms/IOS/**.storyboard) + + list (APPEND application_source ${platform_ios_source}) +endif() + +if (BUILD_DEBUG_VLD) + if (CMAKE_CL_64) + set(vld_dll_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/bin/Win64/*.*") + else() + set(vld_dll_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/bin/Win32/*.*") + endif() +endif() + +if (BUILD_SDL AND MSVC) + if (CMAKE_CL_64) + set(sdl_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/SDL/lib/x64") + set(sdl_dll_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/SDL/lib/x64/*.dll") + else() + set(sdl_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/SDL/lib/x86") + set(sdl_dll_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/SDL/lib/x86/*.dll") + endif() + link_directories(${sdl_lib_path}) +endif() + +if (MINGW OR CYGWIN) + # .rc build + set(CMAKE_RC_COMPILER_INIT windres) + enable_language(RC) + set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") +endif() + +if (BUILD_EMSCRIPTEN) + add_executable(SampleSpine2D + ${application_source} + ${template_path}/Platforms/Emscripten/MainWebGL.cpp + ) +elseif(BUILD_SDL) + add_executable(SampleSpine2D + ${application_source} + ${template_path}/Platforms/SDL2/MainSDL.cpp + ) +elseif(BUILD_LINUX) + add_executable(SampleSpine2D + ${application_source} + ${template_path}/Platforms/Linux/MainLinux.cpp + ) +elseif(BUILD_ANDROID) + add_library(SampleSpine2D SHARED ${application_source}) +elseif (BUILD_WINDOWS_STORE) + add_executable(SampleSpine2D + ${application_source} + ${platform_uwp_asset} + ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D/TemporaryKey.pfx + ${SKYLICHT_ENGINE_SOURCE_DIR}/PrjUWP/Samples/Spine2D/Package.appxmanifest + ) +elseif (MSVC OR CYGWIN OR MINGW) + add_executable(SampleSpine2D WIN32 + ${application_source} + ${template_path}/Platforms/Win32/Skylicht.rc + ) +elseif (BUILD_MACOS) + file(GLOB resources_files "${SKYLICHT_ENGINE_BIN_DIR}/*.zip") + + set(angle_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/Angle/out/MacOS/Release/${CMAKE_OSX_ARCHITECTURES}") + file(GLOB dylib_files "${angle_lib_path}/*.dylib") + + add_executable(SampleSpine2D + ${application_source} + ${resources_files} + ${dylib_files} + ) + + set_target_properties(SampleSpine2D PROPERTIES MACOSX_BUNDLE TRUE) + + target_link_libraries(SampleSpine2D "-framework Cocoa") + + message(STATUS "- Setup project: SampleSpine2D") + foreach(res_file ${resources_files}) + file(RELATIVE_PATH res_path "${SKYLICHT_ENGINE_BIN_DIR}" ${res_file}) + message(STATUS " - Add resources: ${res_path}") + set_property(SOURCE ${res_file} PROPERTY MACOSX_PACKAGE_LOCATION "Resources") + source_group("Bin" FILES "${res_file}") + endforeach() + + foreach(lib_file ${dylib_files}) + set_property(SOURCE ${lib_file} PROPERTY MACOSX_PACKAGE_LOCATION "Frameworks") + set_source_files_properties(${lib_file} PROPERTIES XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") + source_group("Libs" FILES "${lib_file}") + endforeach() + + add_custom_command(TARGET SampleSpine2D + POST_BUILD COMMAND + ${CMAKE_INSTALL_NAME_TOOL} -change ./libEGL.dylib @rpath/libEGL.dylib + $) + + add_custom_command(TARGET SampleSpine2D + POST_BUILD COMMAND + ${CMAKE_INSTALL_NAME_TOOL} -change ./libGLESv2.dylib @rpath/libGLESv2.dylib + $) + + set_target_properties(SampleSpine2D PROPERTIES + MACOSX_RPATH ON + BUILD_WITH_INSTALL_RPATH 1 + INSTALL_RPATH "@executable_path/../Frameworks" + ) +elseif (BUILD_IOS) + set(MACOSX_BUNDLE_EXECUTABLE_NAME SampleSpine2D) + + set(APP_NAME "SampleSpine2D") + set(APP_BUNDLE_IDENTIFIER "com.team_name.project_name") + set(CODE_SIGN_IDENTITY "iPhone Developer") + + set(PRODUCT_NAME ${APP_NAME}) + set(EXECUTABLE_NAME ${APP_NAME}) + set(MACOSX_BUNDLE_EXECUTABLE_NAME ${APP_NAME}) + set(MACOSX_BUNDLE_BUNDLE_NAME ${APP_BUNDLE_IDENTIFIER}) + set(MACOSX_BUNDLE_GUI_IDENTIFIER ${APP_BUNDLE_IDENTIFIER}) + + file(GLOB resources_files "${SKYLICHT_ENGINE_BIN_DIR}/*.zip") + + set(angle_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/Angle/out/IOS/Release/${IOS_PLATFORM}") + file(GLOB angle_framework_files "${angle_lib_path}/*.framework") + + set (framework_files "") + list (APPEND framework_files ${angle_framework_files}) + + add_executable(SampleSpine2D MACOSX_BUNDLE + ${application_source} + ${resources_files} + ${platform_ios_resources} + ${framework_files} + ) + + message(STATUS "- Setup project: SampleSpine2D") + foreach(res_file ${resources_files}) + file(RELATIVE_PATH res_path "${SKYLICHT_ENGINE_BIN_DIR}" ${res_file}) + source_group("Bin" FILES "${res_file}") + endforeach() + + foreach(lib_file ${framework_files}) + source_group("Frameworks" FILES "${lib_file}") + set_property(SOURCE ${lib_file} PROPERTY MACOSX_PACKAGE_LOCATION "Frameworks") + set_source_files_properties(${lib_file} PROPERTIES XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") + endforeach() + + set_target_properties(SampleSpine2D PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${APP_BUNDLE_IDENTIFIER}) + set_target_properties(SampleSpine2D PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${CODE_SIGN_IDENTITY}) + set_target_properties(SampleSpine2D PROPERTIES XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY ${DEVICE_FAMILY}) + set_target_properties(SampleSpine2D PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${template_path}/Platforms/IOS/plist.in") + + # add assets icon + target_sources(SampleSpine2D PRIVATE "${template_path}/Platforms/IOS/Assets.xcassets") + + list (APPEND platform_ios_resources "${template_path}/Platforms/IOS/Assets.xcassets") + set_target_properties(SampleSpine2D PROPERTIES RESOURCE "${resources_files} ${platform_ios_resources}") + + # link framework + target_link_libraries(SampleSpine2D "-framework Foundation") + target_link_libraries(SampleSpine2D "-framework UIKit") + target_link_libraries(SampleSpine2D "-framework CoreGraphics") + target_link_libraries(SampleSpine2D "-framework Metal") + target_link_libraries(SampleSpine2D "-framework MetalKit") + target_link_libraries(SampleSpine2D "-framework AudioToolbox") + target_link_libraries(SampleSpine2D "-framework AVFAudio") +else() + add_executable(SampleSpine2D ${application_source}) +endif() + +target_precompiled_header(SampleSpine2D ${template_path}/pch.cpp ${application_source}) + +# Linker +if (BUILD_ANDROID) + target_link_libraries(SampleSpine2D Client log) +else() + target_link_libraries(SampleSpine2D Client) +endif() + +# Imgui +if (BUILD_IMGUI) + target_link_libraries(SampleSpine2D Imgui) +endif() + +# Spine runtimes +if (BUILD_SPINE_RUNTIMES) + target_link_libraries(SampleSpine2D SpineRuntimes) +endif() + +# Emscripten +if (BUILD_EMSCRIPTEN) + message(STATUS "Setting build in data: ${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${SKYLICHT_ENGINE_BIN_DIR}/BuiltIn" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + + message(STATUS "Setting compilation target to WASM") + set(CMAKE_EXECUTABLE_SUFFIX ".wasm.html") + set_target_properties(SampleSpine2D PROPERTIES LINK_FLAGS "-s USE_SDL=2 -s USE_WEBGL2=1 -s FORCE_FILESYSTEM=1 -s FETCH=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=['_main','_main_resize'] -s EXPORTED_RUNTIME_METHODS=['ccall'] --preload-file ./BuiltIn --shell-file ${template_path}/Platforms/Emscripten/Shell/shell.html --disable-shared -s WASM=1 -s BINARYEN_METHOD='native-wasm'") + + set(project_name SampleSpine2D) + configure_file(${template_path}/Platforms/Emscripten/Index.html ${SKYLICHT_ENGINE_BIN_DIR}/Index.html) + configure_file(${template_path}/Platforms/Emscripten/Index.html ${SKYLICHT_ENGINE_BIN_DIR}/SampleSpine2D.html) +endif() + +#VLD +if (BUILD_DEBUG_VLD) + file(GLOB_RECURSE vld_bin_files ${vld_dll_path}) + foreach(vld_bin ${vld_bin_files}) + add_custom_command(TARGET SampleSpine2D POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${vld_bin} $) + endforeach() +endif() + +#SDL +if (BUILD_SDL AND MSVC) + file(GLOB_RECURSE sdl_bin_files ${sdl_dll_path}) + foreach(sdl_bin ${sdl_bin_files}) + add_custom_command(TARGET SampleSpine2D POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${sdl_bin} $) + endforeach() +endif() + +set_target_properties(SampleSpine2D PROPERTIES VERSION ${SKYLICHT_VERSION}) +set_target_properties(SampleSpine2D PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") diff --git a/Samples/Spine2D/Source/CViewDemo.cpp b/Samples/Spine2D/Source/CViewDemo.cpp new file mode 100644 index 000000000..86a025d0b --- /dev/null +++ b/Samples/Spine2D/Source/CViewDemo.cpp @@ -0,0 +1,115 @@ +#include "pch.h" +#include "CViewDemo.h" + +#include "Context/CContext.h" +#include "CImguiManager.h" +#include "imgui.h" + +#include "CTextureLoader.h" +#include "CSkeletonDrawable.h" + +CViewDemo::CViewDemo() +{ + +} + +CViewDemo::~CViewDemo() +{ + +} + +void CViewDemo::onInit() +{ + CContext* context = CContext::getInstance(); + CCamera* camera = context->getActiveCamera(); + + CScene* scene = context->getScene(); + CZone* zone = scene->getZone(0); + + CGameObject* canvasObj = zone->createEmptyObject(); + CCanvas* canvas = canvasObj->addComponent(); + + CGUIElement* element = canvas->createElement(); + element->setWidth(800.0f); + element->setHeight(400.0f); + element->setAlign(EGUIHorizontalAlign::Center, EGUIVerticalAlign::Middle); + element->setDrawBorder(true); + + io::IFileSystem* fs = getIrrlichtDevice()->getFileSystem(); + + // spine2d + + // load atlas + io::IReadFile* fileAtlas = fs->createAndOpenFile("SampleSpine2D/spineboy-pma.atlas"); + u32 fileSize = fileAtlas->getSize(); + c8* fileData = new c8[fileSize]; + fileAtlas->read(fileData, fileSize); + + spine::CTextureLoader textureLoader; + spine::Atlas atlas(fileData, fileSize, "SampleSpine2D", &textureLoader); + spine::AtlasAttachmentLoader attachmentLoader(&atlas); + spine::SkeletonJson json(&attachmentLoader); + + delete[]fileData; + + // drawable + io::IReadFile* fileJson = fs->createAndOpenFile("SampleSpine2D/spineboy-pro.json"); + + fileSize = fileJson->getSize(); + fileData = new c8[fileSize]; + fileJson->read(fileData, fileSize); + + spine::SkeletonData* skeletonData = json.readSkeletonData(fileData); + if (skeletonData != NULL) + { + spine::CSkeletonDrawable drawable(skeletonData); + } + else + { + os::Printer::log(json.getError().buffer()); + } + + delete[]fileData; + + scene->updateIndexSearchObject(); +} + +void CViewDemo::onDestroy() +{ + +} + +void CViewDemo::onUpdate() +{ + CContext* context = CContext::getInstance(); + CScene* scene = context->getScene(); + if (scene != NULL) + scene->update(); +} + +void CViewDemo::onRender() +{ + CContext* context = CContext::getInstance(); + + CCamera* camera = context->getActiveCamera(); + CCamera* guiCamera = context->getGUICamera(); + + CScene* scene = context->getScene(); + + // render scene + if (camera != NULL && scene != NULL) + { + context->getRenderPipeline()->render(NULL, camera, scene->getEntityManager(), core::recti()); + } + + // render GUI + if (guiCamera != NULL) + { + CGraphics2D::getInstance()->render(guiCamera); + } +} + +void CViewDemo::onPostRender() +{ + +} diff --git a/Samples/Spine2D/Source/CViewDemo.h b/Samples/Spine2D/Source/CViewDemo.h new file mode 100644 index 000000000..255b5c850 --- /dev/null +++ b/Samples/Spine2D/Source/CViewDemo.h @@ -0,0 +1,23 @@ +#pragma once + +#include "ViewManager/CView.h" + +class CViewDemo : public CView +{ +protected: + +public: + CViewDemo(); + + virtual ~CViewDemo(); + + virtual void onInit(); + + virtual void onDestroy(); + + virtual void onUpdate(); + + virtual void onRender(); + + virtual void onPostRender(); +}; \ No newline at end of file diff --git a/Samples/Spine2D/Source/CViewInit.cpp b/Samples/Spine2D/Source/CViewInit.cpp new file mode 100644 index 000000000..4f9583b09 --- /dev/null +++ b/Samples/Spine2D/Source/CViewInit.cpp @@ -0,0 +1,260 @@ +#include "pch.h" +#include "CViewInit.h" +#include "CViewDemo.h" + +#include "ViewManager/CViewManager.h" +#include "Context/CContext.h" + +#include "GridPlane/CGridPlane.h" +#include "SkySun/CSkySun.h" + + +CViewInit::CViewInit() : + m_initState(CViewInit::DownloadBundles), + m_getFile(NULL), + m_downloaded(0), + m_bakeSHLighting(true) +{ + +} + +CViewInit::~CViewInit() +{ + +} + +io::path CViewInit::getBuiltInPath(const char* name) +{ + return getApplication()->getBuiltInPath(name); +} + +void CViewInit::onInit() +{ + CBaseApp* app = getApplication(); + app->showDebugConsole(); + app->getFileSystem()->addFileArchive(getBuiltInPath("BuiltIn.zip"), false, false); + + CShaderManager* shaderMgr = CShaderManager::getInstance(); + shaderMgr->initBasicShader(); + shaderMgr->initSGForwarderShader(); + shaderMgr->initSGDeferredShader(); + + CGlyphFreetype* freetypeFont = CGlyphFreetype::getInstance(); + freetypeFont->initFont("Segoe UI Light", "BuiltIn/Fonts/segoeui/segoeuil.ttf"); + + // init basic gui + CZone* zone = CContext::getInstance()->initScene()->createZone(); + m_guiObject = zone->createEmptyObject(); + CCanvas* canvas = m_guiObject->addComponent(); + + // load font + m_font = new CGlyphFont(); + m_font->setFont("Segoe UI Light", 25); + + // create text + m_textInfo = canvas->createText(m_font); + m_textInfo->setTextAlign(EGUIHorizontalAlign::Center, EGUIVerticalAlign::Middle); + m_textInfo->setText(L"Init assets"); + + // create gui camera + CGameObject* guiCameraObject = zone->createEmptyObject(); + CCamera* guiCamera = guiCameraObject->addComponent(); + guiCamera->setProjectionType(CCamera::OrthoUI); + CContext::getInstance()->setGUICamera(guiCamera); +} + +void CViewInit::initScene() +{ + CBaseApp* app = getApplication(); + + // create a scene + CScene* scene = CContext::getInstance()->getScene(); + CZone* zone = scene->getZone(0); + + // camera + CGameObject* camObj = zone->createEmptyObject(); + camObj->addComponent(); + camObj->addComponent()->setMoveSpeed(2.0f); + camObj->addComponent()->setMoveSpeed(1.0f); + + CCamera* camera = camObj->getComponent(); + camera->setPosition(core::vector3df(0.0f, 5.0f, 10.0f)); + camera->lookAt(core::vector3df(0.0f, 1.0f, 0.0f), core::vector3df(0.0f, 1.0f, 0.0f)); + + // gui camera + CGameObject* guiCameraObject = zone->createEmptyObject(); + CCamera* guiCamera = guiCameraObject->addComponent(); + guiCamera->setProjectionType(CCamera::OrthoUI); + + // sky + CSkySun* skySun = zone->createEmptyObject()->addComponent(); + + // reflection probe + CGameObject* reflectionProbeObj = zone->createEmptyObject(); + CReflectionProbe* reflection = reflectionProbeObj->addComponent(); + reflection->loadStaticTexture("Common/Textures/Sky/PaperMill"); + + // lighting + CGameObject* lightObj = zone->createEmptyObject(); + CDirectionalLight* directionalLight = lightObj->addComponent(); + SColor c(255, 255, 244, 214); + directionalLight->setColor(SColorf(c)); + + CTransformEuler* lightTransform = lightObj->getTransformEuler(); + lightTransform->setPosition(core::vector3df(2.0f, 2.0f, 2.0f)); + + core::vector3df direction = core::vector3df(4.0f, -6.0f, -4.5f); + lightTransform->setOrientation(direction, Transform::Oy); + + // plane + CGameObject* grid = zone->createEmptyObject(); + grid->setName("Plane"); + grid->getTransformEuler()->setScale(core::vector3df(10.0f, 1.0f, 10.0f)); + grid->addComponent(); + + + // rendering + u32 w = app->getWidth(); + u32 h = app->getHeight(); + + CContext* context = CContext::getInstance(); + + context->initRenderPipeline(w, h); + context->setActiveZone(zone); + context->setActiveCamera(camera); + context->setGUICamera(guiCamera); + context->setDirectionalLight(directionalLight); +} + +void CViewInit::onDestroy() +{ + m_guiObject->remove(); + delete m_font; +} + +void CViewInit::onUpdate() +{ + CContext* context = CContext::getInstance(); + + switch (m_initState) + { + case CViewInit::DownloadBundles: + { + io::IFileSystem* fileSystem = getApplication()->getFileSystem(); + + std::vector listBundles; + listBundles.push_back("Common.zip"); + listBundles.push_back("SampleSpine2D.zip"); + +#ifdef __EMSCRIPTEN__ + const char* filename = listBundles[m_downloaded].c_str(); + + if (m_getFile == NULL) + { + m_getFile = new CGetFileURL(filename, filename); + m_getFile->download(CGetFileURL::Get); + + char log[512]; + sprintf(log, "Download asset: %s", filename); + os::Printer::log(log); + } + else + { + char log[512]; + sprintf(log, "Download asset: %s - %d%%", filename, m_getFile->getPercent()); + m_textInfo->setText(log); + + if (m_getFile->getState() == CGetFileURL::Finish) + { + // [bundles].zip + fileSystem->addFileArchive(filename, false, false); + + if (++m_downloaded >= listBundles.size()) + m_initState = CViewInit::InitScene; + else + { + delete m_getFile; + m_getFile = NULL; + } + } + else if (m_getFile->getState() == CGetFileURL::Error) + { + // retry download + delete m_getFile; + m_getFile = NULL; + } + } +#else + + for (std::string& bundle : listBundles) + { + const char* r = bundle.c_str(); + fileSystem->addFileArchive(getBuiltInPath(r), false, false); + } + + m_initState = CViewInit::InitScene; +#endif + } + break; + case CViewInit::InitScene: + { + initScene(); + m_initState = CViewInit::Finished; + } + break; + case CViewInit::Error: + { + // todo nothing with black screen + } + break; + default: + { + CScene* scene = context->getScene(); + if (scene != NULL) + scene->update(); + + CViewManager::getInstance()->getLayer(0)->changeView(); + } + break; + } +} + +void CViewInit::onRender() +{ + if (m_initState == CViewInit::Finished) + { + CContext* context = CContext::getInstance(); + CScene* scene = CContext::getInstance()->getScene(); + CBaseRP* rp = CContext::getInstance()->getRenderPipeline(); + + if (m_bakeSHLighting == true) + { + m_bakeSHLighting = false; + + CZone* zone = scene->getZone(0); + + // light probe + CGameObject* lightProbeObj = zone->createEmptyObject(); + CLightProbe* lightProbe = lightProbeObj->addComponent(); + lightProbeObj->getTransformEuler()->setPosition(core::vector3df(0.0f, 1.0f, 0.0f)); + + CGameObject* bakeCameraObj = scene->getZone(0)->createEmptyObject(); + CCamera* bakeCamera = bakeCameraObj->addComponent(); + scene->updateAddRemoveObject(); + + // bake light probe + Lightmapper::CLightmapper* lm = Lightmapper::CLightmapper::getInstance(); + lm->initBaker(64); + + std::vector probes; + probes.push_back(lightProbe); + + lm->bakeProbes(probes, bakeCamera, rp, scene->getEntityManager()); + } + } + else + { + CCamera* guiCamera = CContext::getInstance()->getGUICamera(); + CGraphics2D::getInstance()->render(guiCamera); + } +} diff --git a/Samples/Spine2D/Source/CViewInit.h b/Samples/Spine2D/Source/CViewInit.h new file mode 100644 index 000000000..cbf0e11a0 --- /dev/null +++ b/Samples/Spine2D/Source/CViewInit.h @@ -0,0 +1,49 @@ +#pragma once + +#include "SkylichtEngine.h" +#include "ViewManager/CView.h" +#include "Emscripten/CGetFileURL.h" + +class CViewInit : public CView +{ +public: + enum EInitState + { + DownloadBundles, + InitScene, + Error, + Finished + }; + +protected: + CGetFileURL* m_getFile; + + bool m_bakeSHLighting; + + EInitState m_initState; + unsigned int m_downloaded; + + CGameObject* m_guiObject; + CGUIText* m_textInfo; + CGlyphFont* m_font; + +protected: + io::path getBuiltInPath(const char* name); + +public: + CViewInit(); + + virtual ~CViewInit(); + + virtual void onInit(); + + virtual void onDestroy(); + + virtual void onUpdate(); + + virtual void onRender(); + +protected: + + void initScene(); +}; diff --git a/Samples/Spine2D/Source/SampleSpine2D.cpp b/Samples/Spine2D/Source/SampleSpine2D.cpp new file mode 100644 index 000000000..d639de6a7 --- /dev/null +++ b/Samples/Spine2D/Source/SampleSpine2D.cpp @@ -0,0 +1,83 @@ +#include "pch.h" +#include "SampleSpine2D.h" + +#include "Context/CContext.h" +#include "ViewManager/CViewManager.h" +#include "CImguiManager.h" + +#include "CViewInit.h" + +void installApplication(const std::vector& argv) +{ + SampleSpine2D* app = new SampleSpine2D(); + getApplication()->registerAppEvent("SampleSpine2D", app); +} + +SampleSpine2D::SampleSpine2D() +{ + CContext::createGetInstance(); + CViewManager::createGetInstance()->initViewLayer(1); + CLightmapper::createGetInstance(); + CImguiManager::createGetInstance(); +} + +SampleSpine2D::~SampleSpine2D() +{ + CViewManager::releaseInstance(); + CContext::releaseInstance(); + CLightmapper::releaseInstance(); + CImguiManager::releaseInstance(); +} + +void SampleSpine2D::onInitApp() +{ + CViewManager::getInstance()->getLayer(0)->pushView(); +} + +void SampleSpine2D::onUpdate() +{ + CViewManager::getInstance()->update(); +} + +void SampleSpine2D::onRender() +{ + CViewManager::getInstance()->render(); +} + +void SampleSpine2D::onPostRender() +{ + // post render application + CViewManager::getInstance()->postRender(); +} + +bool SampleSpine2D::onBack() +{ + // on back key press + // return TRUE will run default by OS (Mobile) + // return FALSE will cancel BACK FUNCTION by OS (Mobile) + return CViewManager::getInstance()->onBack(); +} + +void SampleSpine2D::onResize(int w, int h) +{ + if (CContext::getInstance() != NULL) + CContext::getInstance()->resize(w, h); +} + +void SampleSpine2D::onResume() +{ + // resume application + CViewManager::getInstance()->onResume(); +} + +void SampleSpine2D::onPause() +{ + // pause application + CViewManager::getInstance()->onPause(); +} + +void SampleSpine2D::onQuitApp() +{ + // end application + delete this; +} \ No newline at end of file diff --git a/Samples/Spine2D/Source/SampleSpine2D.h b/Samples/Spine2D/Source/SampleSpine2D.h new file mode 100644 index 000000000..a3d1a5ab3 --- /dev/null +++ b/Samples/Spine2D/Source/SampleSpine2D.h @@ -0,0 +1,28 @@ +#pragma once + +#include "IApplicationEventReceiver.h" + +class SampleSpine2D : public IApplicationEventReceiver +{ +public: + SampleSpine2D(); + virtual ~SampleSpine2D(); + + virtual void onUpdate(); + + virtual void onRender(); + + virtual void onPostRender(); + + virtual void onResume(); + + virtual void onPause(); + + virtual bool onBack(); + + virtual void onResize(int w, int h); + + virtual void onInitApp(); + + virtual void onQuitApp(); +}; \ No newline at end of file diff --git a/Samples/Spine2D/Source/Version.h b/Samples/Spine2D/Source/Version.h new file mode 100644 index 000000000..0cb2aa68b --- /dev/null +++ b/Samples/Spine2D/Source/Version.h @@ -0,0 +1,4 @@ +#pragma once + +#define PROJECT_NAME "CharacterController" +#define PROJECT_VERSION "0.0.2" \ No newline at end of file diff --git a/Scripts/CMakeLists.txt b/Scripts/CMakeLists.txt index c94f8d2cd..d8015e85f 100644 --- a/Scripts/CMakeLists.txt +++ b/Scripts/CMakeLists.txt @@ -18,6 +18,13 @@ if (BUILD_IMGUI) include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Imgui) endif() +if (BUILD_SPINE_RUNTIMES) + include_directories( + ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp/spine-cpp/include + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes + ) +endif() + set(template_path ${SKYLICHT_ENGINE_PROJECT_DIR}/Main) file(GLOB_RECURSE application_source @@ -295,6 +302,11 @@ if (BUILD_IMGUI) target_link_libraries(@project_name@ Imgui) endif() +# Spine runtimes +if (BUILD_SPINE_RUNTIMES) + target_link_libraries(@project_name@ SpineRuntimes) +endif() + # Emscripten if (BUILD_EMSCRIPTEN) message(STATUS "Setting build in data: ${CMAKE_CURRENT_BINARY_DIR}") From d09e58d7faa1f3e484e4b65c65dc4eb4333588b8 Mon Sep 17 00:00:00 2001 From: "duc.phamhong" Date: Thu, 3 Oct 2024 14:41:58 +0700 Subject: [PATCH 3/5] Add SpineLoader --- CMakeLists.txt | 1 - GenerateVCPrj.cmd | 1 + .../Skylicht/SpineRuntimes/CMakeLists.txt | 70 --------- Projects/SpineCpp/CMakeLists.txt | 16 +- .../CSkeletonDrawable.cpp | 0 .../CSkeletonDrawable.h | 0 Projects/SpineCpp/CSpineLoader.cpp | 143 ++++++++++++++++++ Projects/SpineCpp/CSpineLoader.h | 54 +++++++ .../CTextureLoader.cpp | 0 .../CTextureLoader.h | 0 .../SpineRuntimes => SpineCpp}/pch.cpp | 0 .../SpineRuntimes => SpineCpp}/pch.h | 0 Samples/Spine2D/CMakeLists.txt | 2 +- Samples/Spine2D/Source/CViewDemo.cpp | 38 +---- Scripts/CMakeLists.txt | 2 +- 15 files changed, 217 insertions(+), 110 deletions(-) create mode 100644 GenerateVCPrj.cmd delete mode 100644 Projects/Skylicht/SpineRuntimes/CMakeLists.txt rename Projects/{Skylicht/SpineRuntimes => SpineCpp}/CSkeletonDrawable.cpp (100%) rename Projects/{Skylicht/SpineRuntimes => SpineCpp}/CSkeletonDrawable.h (100%) create mode 100644 Projects/SpineCpp/CSpineLoader.cpp create mode 100644 Projects/SpineCpp/CSpineLoader.h rename Projects/{Skylicht/SpineRuntimes => SpineCpp}/CTextureLoader.cpp (100%) rename Projects/{Skylicht/SpineRuntimes => SpineCpp}/CTextureLoader.h (100%) rename Projects/{Skylicht/SpineRuntimes => SpineCpp}/pch.cpp (100%) rename Projects/{Skylicht/SpineRuntimes => SpineCpp}/pch.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55e4bf94a..8ca37bbb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,7 +248,6 @@ endif() # spine runtimes if (BUILD_SPINE_RUNTIMES) subdirs (Projects/SpineCpp) -subdirs (Projects/Skylicht/SpineRuntimes) endif() # skylicht components diff --git a/GenerateVCPrj.cmd b/GenerateVCPrj.cmd new file mode 100644 index 000000000..3cc05f177 --- /dev/null +++ b/GenerateVCPrj.cmd @@ -0,0 +1 @@ +cmake -S . -B ./PrjVisualStudio -G "Visual Studio 17 2022" -A x64 \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CMakeLists.txt b/Projects/Skylicht/SpineRuntimes/CMakeLists.txt deleted file mode 100644 index 8bac2bda1..000000000 --- a/Projects/Skylicht/SpineRuntimes/CMakeLists.txt +++ /dev/null @@ -1,70 +0,0 @@ -include_directories( - ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp/spine-cpp/include - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/System - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes - ${SKYLICHT_ENGINE_PROJECT_DIR}/Irrlicht/Include - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Engine - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Components - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Collision - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Physics - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Lightmapper - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Audio - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/GUI -) - -if (BUILD_DEBUG_VLD) - set(vld_inc_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/include") - include_directories(${vld_inc_path}) - - if (CMAKE_CL_64) - set(vld_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/lib/Win64") - else() - set(vld_lib_path "${SKYLICHT_ENGINE_PROJECT_DIR}/ThirdPartySDK/Vld/lib/Win32") - endif() -endif() - -if (BUILD_SKYLICHT_NETWORK) -include_directories(${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Network) -endif() - -file(GLOB_RECURSE skylicht_spine_source - ./**.cpp - ./**.c - ./**.h) - -setup_project_group("${skylicht_spine_source}" ${CMAKE_CURRENT_SOURCE_DIR}) - -add_library(SpineRuntimes ${ENGINE_SHARED_OR_STATIC_LIB} ${skylicht_spine_source}) - -if (BUILD_SHARED_LIBS) -set_target_properties(SpineRuntimes PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS true) -endif() - -target_precompiled_header(SpineRuntimes ./pch.cpp ${skylicht_spine_source}) - -set_target_properties(SpineRuntimes PROPERTIES VERSION ${SKYLICHT_VERSION}) - -target_link_libraries(SpineRuntimes Engine spine-cpp) - -if (BUILD_DEBUG_VLD) - target_link_libraries(SpineRuntimes vld) - target_link_directories(SpineRuntimes PUBLIC ${vld_lib_path}) -endif() - -if (INSTALL_LIBS) -install(TARGETS SpineRuntimes - EXPORT SpineRuntimesTargets - RUNTIME DESTINATION ${SKYLICHT_LIBRARY_INSTALL_DIR} - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) - -install (DIRECTORY ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes - DESTINATION ${SKYLICHT_INCLUDE_INSTALL_DIR}/Skylicht - FILES_MATCHING PATTERN "*.h*") - -install(EXPORT SpineRuntimesTargets - FILE SpineRuntimesTargets.cmake - NAMESPACE Skylicht:: - DESTINATION ${SKYLICHT_LIBRARY_INSTALL_DIR}/cmake -) -endif() \ No newline at end of file diff --git a/Projects/SpineCpp/CMakeLists.txt b/Projects/SpineCpp/CMakeLists.txt index ed0f00e1a..f46f65281 100644 --- a/Projects/SpineCpp/CMakeLists.txt +++ b/Projects/SpineCpp/CMakeLists.txt @@ -2,9 +2,16 @@ cmake_minimum_required(VERSION 3.10) include(flags.cmake) -include_directories(include) +include_directories( + ${SKYLICHT_ENGINE_PROJECT_DIR}/Irrlicht/Include + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/System + ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/Engine +) + file(GLOB INCLUDES "spine-cpp/include/**/*.h") file(GLOB SOURCES "spine-cpp/src/**/*.cpp") +file(GLOB SL_INCLUDES "*.h") +file(GLOB SL_SOURCES "*.cpp") if (BUILD_SHARED_LIBS) if (BUILD_LINUX OR BUILD_MACOS) @@ -12,5 +19,8 @@ add_compile_options(-fpic) endif() endif() -add_library(spine-cpp STATIC ${SOURCES} ${INCLUDES}) -target_include_directories(spine-cpp PUBLIC spine-cpp/include) \ No newline at end of file +add_library(SpineRuntimes STATIC ${SOURCES} ${INCLUDES} ${SL_SOURCES} ${SL_INCLUDES}) + +target_include_directories(SpineRuntimes PUBLIC spine-cpp/include) + +target_link_libraries(SpineRuntimes Engine) \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.cpp b/Projects/SpineCpp/CSkeletonDrawable.cpp similarity index 100% rename from Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.cpp rename to Projects/SpineCpp/CSkeletonDrawable.cpp diff --git a/Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h b/Projects/SpineCpp/CSkeletonDrawable.h similarity index 100% rename from Projects/Skylicht/SpineRuntimes/CSkeletonDrawable.h rename to Projects/SpineCpp/CSkeletonDrawable.h diff --git a/Projects/SpineCpp/CSpineLoader.cpp b/Projects/SpineCpp/CSpineLoader.cpp new file mode 100644 index 000000000..b1ec04486 --- /dev/null +++ b/Projects/SpineCpp/CSpineLoader.cpp @@ -0,0 +1,143 @@ +/* +!@ +MIT License + +Copyright (c) 2024 Skylicht Technology CO., LTD + +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. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#include "pch.h" +#include "CSpineLoader.h" + +using namespace Skylicht; + +namespace spine +{ + CSpineLoader::CSpineLoader() : + m_textureLoader(NULL), + m_drawable(NULL), + m_atlas(NULL), + m_attachmentLoader(NULL), + m_skeletonJson(NULL), + m_skeletonData(NULL) + { + + } + + CSpineLoader::~CSpineLoader() + { + free(); + } + + bool CSpineLoader::loadAtlas(const char* path, const char* folder) + { + io::IFileSystem* fs = getIrrlichtDevice()->getFileSystem(); + + io::IReadFile* fileAtlas = fs->createAndOpenFile(path); + if (fileAtlas == NULL) + return false; + + u32 fileSize = fileAtlas->getSize(); + c8* fileData = new c8[fileSize]; + fileAtlas->read(fileData, fileSize); + fileAtlas->drop(); + + if (m_textureLoader == NULL) + m_textureLoader = new CTextureLoader(); + + if (m_atlas) + delete m_atlas; + + m_atlas = new spine::Atlas(fileData, fileSize, folder, m_textureLoader); + + delete[]fileData; + return true; + } + + bool CSpineLoader::loadSkeletonJson(const char* path) + { + if (m_atlas == NULL) + { + os::Printer::log("[CSpineLoader] loadSkeletonJson with NULL atlas"); + return false; + } + + io::IFileSystem* fs = getIrrlichtDevice()->getFileSystem(); + io::IReadFile* fileJson = fs->createAndOpenFile(path); + if (fileJson == NULL) + return false; + + if (m_attachmentLoader) + delete m_attachmentLoader; + m_attachmentLoader = new spine::AtlasAttachmentLoader(m_atlas); + + if (m_skeletonJson) + delete m_skeletonJson; + m_skeletonJson = new spine::SkeletonJson(m_attachmentLoader); + + if (m_drawable) + { + delete m_drawable; + m_drawable = NULL; + } + + u32 fileSize = fileJson->getSize(); + c8* fileData = new c8[fileSize]; + fileJson->read(fileData, fileSize); + fileJson->drop(); + + m_skeletonData = m_skeletonJson->readSkeletonData(fileData); + if (m_skeletonData != NULL) + { + m_drawable = new spine::CSkeletonDrawable(m_skeletonData); + } + else + { + os::Printer::log("[CSpineLoader] loadSkeletonJson load json failed!"); + os::Printer::log(m_skeletonJson->getError().buffer()); + } + + delete[]fileData; + return true; + } + + void CSpineLoader::free() + { + if (m_skeletonJson) + delete m_skeletonJson; + + if (m_attachmentLoader) + delete m_attachmentLoader; + + if (m_atlas) + delete m_atlas; + + if (m_textureLoader) + delete m_textureLoader; + + if (m_drawable) + delete m_drawable; + + m_skeletonJson = NULL; + m_attachmentLoader = NULL; + m_atlas = NULL; + m_textureLoader = NULL; + m_drawable = NULL; + } +} \ No newline at end of file diff --git a/Projects/SpineCpp/CSpineLoader.h b/Projects/SpineCpp/CSpineLoader.h new file mode 100644 index 000000000..4b639035c --- /dev/null +++ b/Projects/SpineCpp/CSpineLoader.h @@ -0,0 +1,54 @@ +/* +!@ +MIT License + +Copyright (c) 2024 Skylicht Technology CO., LTD + +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. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#pragma once + +#include "CTextureLoader.h" +#include "CSkeletonDrawable.h" + +namespace spine +{ + class CSpineLoader + { + protected: + CTextureLoader* m_textureLoader; + CSkeletonDrawable* m_drawable; + + spine::Atlas* m_atlas; + spine::AtlasAttachmentLoader* m_attachmentLoader; + spine::SkeletonJson* m_skeletonJson; + spine::SkeletonData* m_skeletonData; + + public: + CSpineLoader(); + + virtual ~CSpineLoader(); + + bool loadAtlas(const char* path, const char* folder); + + bool loadSkeletonJson(const char* path); + + void free(); + }; +} \ No newline at end of file diff --git a/Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp b/Projects/SpineCpp/CTextureLoader.cpp similarity index 100% rename from Projects/Skylicht/SpineRuntimes/CTextureLoader.cpp rename to Projects/SpineCpp/CTextureLoader.cpp diff --git a/Projects/Skylicht/SpineRuntimes/CTextureLoader.h b/Projects/SpineCpp/CTextureLoader.h similarity index 100% rename from Projects/Skylicht/SpineRuntimes/CTextureLoader.h rename to Projects/SpineCpp/CTextureLoader.h diff --git a/Projects/Skylicht/SpineRuntimes/pch.cpp b/Projects/SpineCpp/pch.cpp similarity index 100% rename from Projects/Skylicht/SpineRuntimes/pch.cpp rename to Projects/SpineCpp/pch.cpp diff --git a/Projects/Skylicht/SpineRuntimes/pch.h b/Projects/SpineCpp/pch.h similarity index 100% rename from Projects/Skylicht/SpineRuntimes/pch.h rename to Projects/SpineCpp/pch.h diff --git a/Samples/Spine2D/CMakeLists.txt b/Samples/Spine2D/CMakeLists.txt index 7a97642f5..130122551 100644 --- a/Samples/Spine2D/CMakeLists.txt +++ b/Samples/Spine2D/CMakeLists.txt @@ -22,7 +22,7 @@ endif() if (BUILD_SPINE_RUNTIMES) include_directories( ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp/spine-cpp/include - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes + ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp ) endif() diff --git a/Samples/Spine2D/Source/CViewDemo.cpp b/Samples/Spine2D/Source/CViewDemo.cpp index 86a025d0b..3321c7dd8 100644 --- a/Samples/Spine2D/Source/CViewDemo.cpp +++ b/Samples/Spine2D/Source/CViewDemo.cpp @@ -5,8 +5,7 @@ #include "CImguiManager.h" #include "imgui.h" -#include "CTextureLoader.h" -#include "CSkeletonDrawable.h" +#include "CSpineLoader.h" CViewDemo::CViewDemo() { @@ -38,38 +37,9 @@ void CViewDemo::onInit() io::IFileSystem* fs = getIrrlichtDevice()->getFileSystem(); // spine2d - - // load atlas - io::IReadFile* fileAtlas = fs->createAndOpenFile("SampleSpine2D/spineboy-pma.atlas"); - u32 fileSize = fileAtlas->getSize(); - c8* fileData = new c8[fileSize]; - fileAtlas->read(fileData, fileSize); - - spine::CTextureLoader textureLoader; - spine::Atlas atlas(fileData, fileSize, "SampleSpine2D", &textureLoader); - spine::AtlasAttachmentLoader attachmentLoader(&atlas); - spine::SkeletonJson json(&attachmentLoader); - - delete[]fileData; - - // drawable - io::IReadFile* fileJson = fs->createAndOpenFile("SampleSpine2D/spineboy-pro.json"); - - fileSize = fileJson->getSize(); - fileData = new c8[fileSize]; - fileJson->read(fileData, fileSize); - - spine::SkeletonData* skeletonData = json.readSkeletonData(fileData); - if (skeletonData != NULL) - { - spine::CSkeletonDrawable drawable(skeletonData); - } - else - { - os::Printer::log(json.getError().buffer()); - } - - delete[]fileData; + spine::CSpineLoader spineLoader; + spineLoader.loadAtlas("SampleSpine2D/spineboy-pma.atlas", "SampleSpine2D"); + spineLoader.loadSkeletonJson("SampleSpine2D/spineboy-pro.json"); scene->updateIndexSearchObject(); } diff --git a/Scripts/CMakeLists.txt b/Scripts/CMakeLists.txt index d8015e85f..294a7e24e 100644 --- a/Scripts/CMakeLists.txt +++ b/Scripts/CMakeLists.txt @@ -21,7 +21,7 @@ endif() if (BUILD_SPINE_RUNTIMES) include_directories( ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp/spine-cpp/include - ${SKYLICHT_ENGINE_PROJECT_DIR}/Skylicht/SpineRuntimes + ${SKYLICHT_ENGINE_PROJECT_DIR}/SpineCpp ) endif() From cbd797f1d9cae766a06a35770b603e42b61ac519 Mon Sep 17 00:00:00 2001 From: "duc.phamhong" Date: Thu, 3 Oct 2024 15:06:30 +0700 Subject: [PATCH 4/5] Fix build error --- .../Skylicht/Engine/Graphics2D/CGraphics2D.h | 5 +++++ .../{CSpineLoader.cpp => CSpineResource.cpp} | 16 ++++++++-------- .../{CSpineLoader.h => CSpineResource.h} | 11 ++++++++--- Projects/SpineCpp/flags.cmake | 4 ++-- Samples/Spine2D/Source/CViewDemo.cpp | 8 ++++---- 5 files changed, 27 insertions(+), 17 deletions(-) rename Projects/SpineCpp/{CSpineLoader.cpp => CSpineResource.cpp} (88%) rename Projects/SpineCpp/{CSpineLoader.h => CSpineResource.h} (91%) diff --git a/Projects/Skylicht/Engine/Graphics2D/CGraphics2D.h b/Projects/Skylicht/Engine/Graphics2D/CGraphics2D.h index d71f1d94a..d18eb393d 100644 --- a/Projects/Skylicht/Engine/Graphics2D/CGraphics2D.h +++ b/Projects/Skylicht/Engine/Graphics2D/CGraphics2D.h @@ -188,6 +188,11 @@ namespace Skylicht return m_2dMaterial; } + IMeshBuffer* getCurrentBuffer() + { + return m_buffer; + } + private: void updateRectBuffer(video::S3DVertex* vtx, const core::rectf& r, const core::matrix4& mat); diff --git a/Projects/SpineCpp/CSpineLoader.cpp b/Projects/SpineCpp/CSpineResource.cpp similarity index 88% rename from Projects/SpineCpp/CSpineLoader.cpp rename to Projects/SpineCpp/CSpineResource.cpp index b1ec04486..6d39bc8ce 100644 --- a/Projects/SpineCpp/CSpineLoader.cpp +++ b/Projects/SpineCpp/CSpineResource.cpp @@ -23,13 +23,13 @@ This file is part of the "Skylicht Engine". */ #include "pch.h" -#include "CSpineLoader.h" +#include "CSpineResource.h" using namespace Skylicht; namespace spine { - CSpineLoader::CSpineLoader() : + CSpineResource::CSpineResource() : m_textureLoader(NULL), m_drawable(NULL), m_atlas(NULL), @@ -40,12 +40,12 @@ namespace spine } - CSpineLoader::~CSpineLoader() + CSpineResource::~CSpineResource() { free(); } - bool CSpineLoader::loadAtlas(const char* path, const char* folder) + bool CSpineResource::loadAtlas(const char* path, const char* folder) { io::IFileSystem* fs = getIrrlichtDevice()->getFileSystem(); @@ -70,11 +70,11 @@ namespace spine return true; } - bool CSpineLoader::loadSkeletonJson(const char* path) + bool CSpineResource::loadSkeletonJson(const char* path) { if (m_atlas == NULL) { - os::Printer::log("[CSpineLoader] loadSkeletonJson with NULL atlas"); + os::Printer::log("[CSpineResource] loadSkeletonJson with NULL atlas"); return false; } @@ -109,7 +109,7 @@ namespace spine } else { - os::Printer::log("[CSpineLoader] loadSkeletonJson load json failed!"); + os::Printer::log("[CSpineResource] loadSkeletonJson load json failed!"); os::Printer::log(m_skeletonJson->getError().buffer()); } @@ -117,7 +117,7 @@ namespace spine return true; } - void CSpineLoader::free() + void CSpineResource::free() { if (m_skeletonJson) delete m_skeletonJson; diff --git a/Projects/SpineCpp/CSpineLoader.h b/Projects/SpineCpp/CSpineResource.h similarity index 91% rename from Projects/SpineCpp/CSpineLoader.h rename to Projects/SpineCpp/CSpineResource.h index 4b639035c..2cca14603 100644 --- a/Projects/SpineCpp/CSpineLoader.h +++ b/Projects/SpineCpp/CSpineResource.h @@ -29,7 +29,7 @@ This file is part of the "Skylicht Engine". namespace spine { - class CSpineLoader + class CSpineResource { protected: CTextureLoader* m_textureLoader; @@ -41,14 +41,19 @@ namespace spine spine::SkeletonData* m_skeletonData; public: - CSpineLoader(); + CSpineResource(); - virtual ~CSpineLoader(); + virtual ~CSpineResource(); bool loadAtlas(const char* path, const char* folder); bool loadSkeletonJson(const char* path); void free(); + + inline CSkeletonDrawable* getDrawable() + { + return m_drawable; + } }; } \ No newline at end of file diff --git a/Projects/SpineCpp/flags.cmake b/Projects/SpineCpp/flags.cmake index 07ecfe486..0c1eb3aa7 100644 --- a/Projects/SpineCpp/flags.cmake +++ b/Projects/SpineCpp/flags.cmake @@ -5,8 +5,8 @@ if(MSVC) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS") else() - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -std=c99") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wnon-virtual-dtor -pedantic -Wno-unused-parameter -std=c++11 -fno-exceptions -fno-rtti") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter -std=c99") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wnon-virtual-dtor -Wno-unused-parameter -std=c++11 -fno-exceptions") if (${SPINE_SANITIZE}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=undefined") diff --git a/Samples/Spine2D/Source/CViewDemo.cpp b/Samples/Spine2D/Source/CViewDemo.cpp index 3321c7dd8..63d5a71db 100644 --- a/Samples/Spine2D/Source/CViewDemo.cpp +++ b/Samples/Spine2D/Source/CViewDemo.cpp @@ -5,7 +5,7 @@ #include "CImguiManager.h" #include "imgui.h" -#include "CSpineLoader.h" +#include "CSpineResource.h" CViewDemo::CViewDemo() { @@ -37,9 +37,9 @@ void CViewDemo::onInit() io::IFileSystem* fs = getIrrlichtDevice()->getFileSystem(); // spine2d - spine::CSpineLoader spineLoader; - spineLoader.loadAtlas("SampleSpine2D/spineboy-pma.atlas", "SampleSpine2D"); - spineLoader.loadSkeletonJson("SampleSpine2D/spineboy-pro.json"); + spine::CSpineResource spineRes; + spineRes.loadAtlas("SampleSpine2D/spineboy-pma.atlas", "SampleSpine2D"); + spineRes.loadSkeletonJson("SampleSpine2D/spineboy-pro.json"); scene->updateIndexSearchObject(); } From ad0573b522d46faf72a52279b4f05a606810f781 Mon Sep 17 00:00:00 2001 From: "duc.phamhong" Date: Thu, 3 Oct 2024 17:25:08 +0700 Subject: [PATCH 5/5] Done spine runtimes --- Projects/Irrlicht/Include/EMaterialTypes.h | 4 + Projects/SpineCpp/CSkeletonDrawable.cpp | 101 ++++++++++++++++++++- Projects/SpineCpp/CSkeletonDrawable.h | 26 +++++- Projects/SpineCpp/CSpineResource.cpp | 61 ++++++++++++- Projects/SpineCpp/CSpineResource.h | 20 +++- Projects/SpineCpp/CTextureLoader.cpp | 7 +- Projects/SpineCpp/CTextureLoader.h | 3 + Samples/Spine2D/Source/CViewDemo.cpp | 49 ++++++++-- Samples/Spine2D/Source/CViewDemo.h | 8 ++ 9 files changed, 258 insertions(+), 21 deletions(-) diff --git a/Projects/Irrlicht/Include/EMaterialTypes.h b/Projects/Irrlicht/Include/EMaterialTypes.h index ec5b6ae51..e2961e3d8 100644 --- a/Projects/Irrlicht/Include/EMaterialTypes.h +++ b/Projects/Irrlicht/Include/EMaterialTypes.h @@ -57,6 +57,10 @@ namespace video //! Makes the material transparent based on the vertex alpha value. EMT_TRANSPARENT_VERTEX_ALPHA, + //! Skylicht engine update + EMT_TRANSPARENT_MULTIPLY, + EMT_TRANSPARENT_SCREEN, + //! This value is not used. It only forces this enumeration to compile to 32 bit. EMT_FORCE_32BIT = 0x7fffffff }; diff --git a/Projects/SpineCpp/CSkeletonDrawable.cpp b/Projects/SpineCpp/CSkeletonDrawable.cpp index 14b9b20ab..74419bfdb 100644 --- a/Projects/SpineCpp/CSkeletonDrawable.cpp +++ b/Projects/SpineCpp/CSkeletonDrawable.cpp @@ -24,6 +24,11 @@ This file is part of the "Skylicht Engine". #include "pch.h" #include "CSkeletonDrawable.h" +#include "CSpineResource.h" + +#include "Graphics2D/CGraphics2D.h" +#include "Graphics2D/CCanvas.h" +#include "Graphics2D/GUI/CGUIElement.h" using namespace Skylicht; @@ -36,7 +41,6 @@ namespace spine CSkeletonDrawable::CSkeletonDrawable(spine::SkeletonData* skeletonData, spine::AnimationStateData* animationStateData) : m_skeleton(NULL), m_animationState(NULL), - m_usePremultipliedAlpha(false), m_ownsAnimationStateData(false) { m_skeleton = new spine::Skeleton(skeletonData); @@ -59,14 +63,103 @@ namespace spine void CSkeletonDrawable::update(float delta, spine::Physics physics) { - m_animationState->update(delta); + m_animationState->update(delta * 0.001f); m_animationState->apply(*m_skeleton); m_skeleton->update(delta); m_skeleton->updateWorldTransform(physics); } - void CSkeletonDrawable::render() + void CSkeletonDrawable::render(CGUIElement* insideElement) { - + if (insideElement == NULL) + return; + + spine::SkeletonRenderer* renderer = CSpineResource::getRenderer(); + if (renderer == NULL) + return; + + CCanvas* canvas = insideElement->getCanvas(); + float canvasHeight = canvas->getRootElement()->getHeight(); + + // move to center of element + core::vector3df pos = insideElement->getAbsoluteTransform().getTranslation(); + pos.X = pos.X + insideElement->getWidth() * 0.5f + m_drawOffset.X; + pos.Y = pos.Y + insideElement->getHeight() * 0.5f + m_drawOffset.Y; + + // set position (flip Y) + m_skeleton->setPosition(pos.X, canvasHeight - pos.Y); + + // flush the current buffer + CGraphics2D* graphics = CGraphics2D::getInstance(); + video::SMaterial& material = graphics->getMaterial(); + graphics->flush(); + + RenderCommand* command = renderer->render(*m_skeleton); + while (command) + { + IMeshBuffer* meshBuffer = graphics->getCurrentBuffer(); + + scene::IVertexBuffer* vtxBuffer = meshBuffer->getVertexBuffer(); + scene::IIndexBuffer* idxBuffer = meshBuffer->getIndexBuffer(); + + float* positions = command->positions; + float* uvs = command->uvs; + uint32_t* colors = command->colors; + + // alloc vertex buffer + vtxBuffer->set_used(command->numVertices); + S3DVertex* vertices = (S3DVertex*)vtxBuffer->getVertices(); + + for (int ii = 0; ii < command->numVertices << 1; ii += 2) + { + S3DVertex* v = &vertices[ii >> 1]; + + v->Pos.X = positions[ii]; + // warning: flip Y + v->Pos.Y = canvasHeight - positions[ii + 1]; + v->Pos.Z = 0.0f; + + v->TCoords.X = uvs[ii]; + v->TCoords.Y = uvs[ii + 1]; + + v->Color.set(colors[ii >> 1]); + } + + // alloc index buffer + idxBuffer->set_used(command->numIndices); + u16* index = (u16*)idxBuffer->getIndices(); + uint16_t* indices = command->indices; + + for (int ii = 0; ii < command->numIndices; ii++) + { + index[ii] = indices[ii]; + } + + // texture + material.setTexture(0, (ITexture*)command->texture); + + // blendmode + BlendMode blendMode = command->blendMode; + switch (blendMode) + { + case BlendMode_Normal: + material.MaterialType = CSpineResource::getTextureColorBlend(); + break; + case BlendMode_Multiply: + material.MaterialType = CSpineResource::getTextureColorMultiply(); + break; + case BlendMode_Additive: + material.MaterialType = CSpineResource::getTextureColorAddtive(); + break; + case BlendMode_Screen: + material.MaterialType = CSpineResource::getTextureColorScreen(); + break; + } + + // draw this command + graphics->flush(); + + command = command->next; + } } } \ No newline at end of file diff --git a/Projects/SpineCpp/CSkeletonDrawable.h b/Projects/SpineCpp/CSkeletonDrawable.h index 6ab74f516..f46898f04 100644 --- a/Projects/SpineCpp/CSkeletonDrawable.h +++ b/Projects/SpineCpp/CSkeletonDrawable.h @@ -25,7 +25,11 @@ This file is part of the "Skylicht Engine". #pragma once #include -#include "Graphics2D/GUI/CGUIElement.h" + +namespace Skylicht +{ + class CGUIElement; +} namespace spine { @@ -35,9 +39,10 @@ namespace spine spine::Skeleton* m_skeleton; spine::AnimationState* m_animationState; - bool m_usePremultipliedAlpha; bool m_ownsAnimationStateData; + core::vector2df m_drawOffset; + public: CSkeletonDrawable(spine::SkeletonData* skeletonData, spine::AnimationStateData* animationStateData = NULL); @@ -45,6 +50,21 @@ namespace spine void update(float delta, spine::Physics physics); - void render(); + void render(Skylicht::CGUIElement* insideElement); + + inline spine::Skeleton* getSkeleton() + { + return m_skeleton; + } + + inline spine::AnimationState* getAnimationState() + { + return m_animationState; + } + + inline void setDrawOffset(const core::vector2df& offset) + { + m_drawOffset = offset; + } }; } \ No newline at end of file diff --git a/Projects/SpineCpp/CSpineResource.cpp b/Projects/SpineCpp/CSpineResource.cpp index 6d39bc8ce..95549081d 100644 --- a/Projects/SpineCpp/CSpineResource.cpp +++ b/Projects/SpineCpp/CSpineResource.cpp @@ -24,11 +24,68 @@ This file is part of the "Skylicht Engine". #include "pch.h" #include "CSpineResource.h" +#include "Material/Shader/CShaderManager.h" using namespace Skylicht; namespace spine { + spine::SkeletonRenderer* g_renderer = NULL; + + spine::SkeletonRenderer* CSpineResource::initRenderer() + { + if (g_renderer == NULL) + g_renderer = new spine::SkeletonRenderer(); + return g_renderer; + } + + void CSpineResource::releaseRenderer() + { + if (g_renderer) + { + delete g_renderer; + g_renderer = NULL; + } + } + + spine::SkeletonRenderer* CSpineResource::getRenderer() + { + return g_renderer; + } + + int g_textColorBlend = 0; + int g_textColorAddtive = 0; + int g_textColorMultiply = 0; + int g_textColorScreen = 0; + + int CSpineResource::getTextureColorBlend() + { + if (g_textColorBlend == 0) + g_textColorBlend = CShaderManager::getInstance()->getShaderIDByName("TextureColorAlpha"); + return g_textColorBlend; + } + + int CSpineResource::getTextureColorAddtive() + { + if (g_textColorAddtive == 0) + g_textColorAddtive = CShaderManager::getInstance()->getShaderIDByName("TextureColorAdditive"); + return g_textColorAddtive; + } + + int CSpineResource::getTextureColorMultiply() + { + if (g_textColorMultiply == 0) + g_textColorMultiply = CShaderManager::getInstance()->getShaderIDByName("TextureColorAlpha"); + return g_textColorMultiply; + } + + int CSpineResource::getTextureColorScreen() + { + if (g_textColorScreen == 0) + g_textColorScreen = CShaderManager::getInstance()->getShaderIDByName("TextureColorAlpha"); + return g_textColorScreen; + } + CSpineResource::CSpineResource() : m_textureLoader(NULL), m_drawable(NULL), @@ -70,7 +127,7 @@ namespace spine return true; } - bool CSpineResource::loadSkeletonJson(const char* path) + bool CSpineResource::loadSkeletonJson(const char* path, float scale) { if (m_atlas == NULL) { @@ -89,7 +146,9 @@ namespace spine if (m_skeletonJson) delete m_skeletonJson; + m_skeletonJson = new spine::SkeletonJson(m_attachmentLoader); + m_skeletonJson->setScale(scale); if (m_drawable) { diff --git a/Projects/SpineCpp/CSpineResource.h b/Projects/SpineCpp/CSpineResource.h index 2cca14603..cb1de4c5e 100644 --- a/Projects/SpineCpp/CSpineResource.h +++ b/Projects/SpineCpp/CSpineResource.h @@ -24,8 +24,8 @@ This file is part of the "Skylicht Engine". #pragma once -#include "CTextureLoader.h" #include "CSkeletonDrawable.h" +#include "CTextureLoader.h" namespace spine { @@ -40,6 +40,22 @@ namespace spine spine::SkeletonJson* m_skeletonJson; spine::SkeletonData* m_skeletonData; + public: + + static spine::SkeletonRenderer* initRenderer(); + + static void releaseRenderer(); + + static spine::SkeletonRenderer* getRenderer(); + + static int getTextureColorBlend(); + + static int getTextureColorAddtive(); + + static int getTextureColorMultiply(); + + static int getTextureColorScreen(); + public: CSpineResource(); @@ -47,7 +63,7 @@ namespace spine bool loadAtlas(const char* path, const char* folder); - bool loadSkeletonJson(const char* path); + bool loadSkeletonJson(const char* path, float scale); void free(); diff --git a/Projects/SpineCpp/CTextureLoader.cpp b/Projects/SpineCpp/CTextureLoader.cpp index c94252d9f..a2bca51f3 100644 --- a/Projects/SpineCpp/CTextureLoader.cpp +++ b/Projects/SpineCpp/CTextureLoader.cpp @@ -23,8 +23,9 @@ This file is part of the "Skylicht Engine". */ #include "pch.h" -#include "CTextureLoader.h" +#include +#include "CTextureLoader.h" #include "TextureManager/CTextureManager.h" using namespace Skylicht; @@ -42,6 +43,10 @@ namespace spine if (!texture) return; page.texture = texture; + + const core::dimension2du& size = texture->getSize(); + page.width = (int)size.Width; + page.height = (int)size.Height; } void CTextureLoader::unload(void* texture) diff --git a/Projects/SpineCpp/CTextureLoader.h b/Projects/SpineCpp/CTextureLoader.h index c88ba8f8e..36b0ae6df 100644 --- a/Projects/SpineCpp/CTextureLoader.h +++ b/Projects/SpineCpp/CTextureLoader.h @@ -28,6 +28,9 @@ This file is part of the "Skylicht Engine". namespace spine { + class AtlasPage; + class String; + class CTextureLoader : public spine::TextureLoader { public: diff --git a/Samples/Spine2D/Source/CViewDemo.cpp b/Samples/Spine2D/Source/CViewDemo.cpp index 63d5a71db..e43a0e56d 100644 --- a/Samples/Spine2D/Source/CViewDemo.cpp +++ b/Samples/Spine2D/Source/CViewDemo.cpp @@ -5,16 +5,18 @@ #include "CImguiManager.h" #include "imgui.h" -#include "CSpineResource.h" - -CViewDemo::CViewDemo() +CViewDemo::CViewDemo() : + m_spineResource(NULL) { - + spine::CSpineResource::initRenderer(); } CViewDemo::~CViewDemo() { + if (m_spineResource) + delete m_spineResource; + spine::CSpineResource::releaseRenderer(); } void CViewDemo::onInit() @@ -28,18 +30,33 @@ void CViewDemo::onInit() CGameObject* canvasObj = zone->createEmptyObject(); CCanvas* canvas = canvasObj->addComponent(); + // this is the gui element, that will render spine inside CGUIElement* element = canvas->createElement(); - element->setWidth(800.0f); + element->setWidth(400.0f); element->setHeight(400.0f); element->setAlign(EGUIHorizontalAlign::Center, EGUIVerticalAlign::Middle); element->setDrawBorder(true); - io::IFileSystem* fs = getIrrlichtDevice()->getFileSystem(); + // init spine2d + m_spineResource = new spine::CSpineResource(); + if (m_spineResource->loadAtlas("SampleSpine2D/spineboy-pma.atlas", "SampleSpine2D")) + { + if (m_spineResource->loadSkeletonJson("SampleSpine2D/spineboy-pro.json", 0.5f)) + { + spine::CSkeletonDrawable* drawable = m_spineResource->getDrawable(); + spine::Skeleton* skeleton = drawable->getSkeleton(); + spine::AnimationState* animationState = drawable->getAnimationState(); + + animationState->getData()->setDefaultMix(0.2f); + skeleton->setToSetupPose(); + + animationState->setAnimation(0, "portal", true); + animationState->addAnimation(0, "run", true, 0); - // spine2d - spine::CSpineResource spineRes; - spineRes.loadAtlas("SampleSpine2D/spineboy-pma.atlas", "SampleSpine2D"); - spineRes.loadSkeletonJson("SampleSpine2D/spineboy-pro.json"); + // callback for draw spine + element->OnRender = std::bind(&CViewDemo::renderSpine, this, std::placeholders::_1); + } + } scene->updateIndexSearchObject(); } @@ -79,6 +96,18 @@ void CViewDemo::onRender() } } +void CViewDemo::renderSpine(CGUIElement* element) +{ + if (m_spineResource) + { + spine::CSkeletonDrawable* drawable = m_spineResource->getDrawable(); + + drawable->setDrawOffset(core::vector2df(0.0f, element->getHeight() * 0.5f)); + drawable->update(getTimeStep(), spine::Physics_Update); + drawable->render(element); + } +} + void CViewDemo::onPostRender() { diff --git a/Samples/Spine2D/Source/CViewDemo.h b/Samples/Spine2D/Source/CViewDemo.h index 255b5c850..55fe3007f 100644 --- a/Samples/Spine2D/Source/CViewDemo.h +++ b/Samples/Spine2D/Source/CViewDemo.h @@ -1,10 +1,13 @@ #pragma once #include "ViewManager/CView.h" +#include "Graphics2D/GUI/CGUIElement.h" +#include "CSpineResource.h" class CViewDemo : public CView { protected: + spine::CSpineResource* m_spineResource; public: CViewDemo(); @@ -20,4 +23,9 @@ class CViewDemo : public CView virtual void onRender(); virtual void onPostRender(); + +protected: + + void renderSpine(CGUIElement *element); + }; \ No newline at end of file