diff --git a/spine-ios/Example/Spine iOS Example.xcodeproj/project.pbxproj b/spine-ios/Example/Spine iOS Example.xcodeproj/project.pbxproj index 7ecf1f5ad..3c8134d35 100644 --- a/spine-ios/Example/Spine iOS Example.xcodeproj/project.pbxproj +++ b/spine-ios/Example/Spine iOS Example.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 76381FDB2C2EF4F10087712B /* spineboy-pma.atlas in Resources */ = {isa = PBXBuildFile; fileRef = 76381FD52C2EF4F00087712B /* spineboy-pma.atlas */; }; 76381FDE2C2EF53A0087712B /* mix-and-match-pma.png in Resources */ = {isa = PBXBuildFile; fileRef = 76381FDC2C2EF53A0087712B /* mix-and-match-pma.png */; }; 76381FDF2C2EF53A0087712B /* mix-and-match-pma.atlas in Resources */ = {isa = PBXBuildFile; fileRef = 76381FDD2C2EF53A0087712B /* mix-and-match-pma.atlas */; }; + 76DD66952E5DE75400963397 /* SpineiOS in Frameworks */ = {isa = PBXBuildFile; productRef = 76DD66942E5DE75400963397 /* SpineiOS */; }; 9205FCD42C0760B1006EE07E /* SimpleAnimationViewControllerRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9205FCD32C0760B1006EE07E /* SimpleAnimationViewControllerRepresentable.swift */; }; 920BD1162BEBC52D0050A5A9 /* spineboy-pro.skel in Resources */ = {isa = PBXBuildFile; fileRef = 920BD1122BEBC52D0050A5A9 /* spineboy-pro.skel */; }; 920BD1182BEBC52D0050A5A9 /* spineboy-pro.json in Resources */ = {isa = PBXBuildFile; fileRef = 920BD1142BEBC52D0050A5A9 /* spineboy-pro.json */; }; @@ -33,10 +34,8 @@ 924C0C182BCFCF21004E63F7 /* SimpleAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924C0C172BCFCF21004E63F7 /* SimpleAnimation.swift */; }; 924C0C1A2BCFCF22004E63F7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 924C0C192BCFCF22004E63F7 /* Assets.xcassets */; }; 92579E772C1B0E9500FDC7D5 /* DisableRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92579E762C1B0E9500FDC7D5 /* DisableRendering.swift */; }; - 925CB7E92C19BC5A00C8F47F /* Spine in Frameworks */ = {isa = PBXBuildFile; productRef = 925CB7E82C19BC5A00C8F47F /* Spine */; }; 9270C16E2BFE356000BD25BC /* Physics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9270C16D2BFE356000BD25BC /* Physics.swift */; }; 9270C1742BFE389600BD25BC /* celestial-circus-pro.skel in Resources */ = {isa = PBXBuildFile; fileRef = 9270C1712BFE389600BD25BC /* celestial-circus-pro.skel */; }; - 928A8CC22BCFE7DF00D9D35B /* Spine in Frameworks */ = {isa = PBXBuildFile; productRef = 928A8CC12BCFE7DF00D9D35B /* Spine */; }; 92D7DDA82BFF3C8800EB9E3A /* DebugRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D7DDA72BFF3C8800EB9E3A /* DebugRendering.swift */; }; 92FE93292BF4AB9600CCDF48 /* IKFollowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FE93282BF4AB9600CCDF48 /* IKFollowing.swift */; }; /* End PBXBuildFile section */ @@ -83,8 +82,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 925CB7E92C19BC5A00C8F47F /* Spine in Frameworks */, - 928A8CC22BCFE7DF00D9D35B /* Spine in Frameworks */, + 76DD66952E5DE75400963397 /* SpineiOS in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -212,8 +210,7 @@ ); name = "Spine iOS Example"; packageProductDependencies = ( - 928A8CC12BCFE7DF00D9D35B /* Spine */, - 925CB7E82C19BC5A00C8F47F /* Spine */, + 76DD66942E5DE75400963397 /* SpineiOS */, ); productName = "Spine iOS Example"; productReference = 924C0C122BCFCF21004E63F7 /* Spine iOS Example.app */; @@ -245,7 +242,7 @@ ); mainGroup = 924C0C092BCFCF21004E63F7; packageReferences = ( - 925CB7E72C19BC5A00C8F47F /* XCLocalSwiftPackageReference "../.." */, + 76DD668F2E5DE75400963397 /* XCLocalSwiftPackageReference "../../../spine-runtimes" */, ); productRefGroup = 924C0C132BCFCF21004E63F7 /* Products */; projectDirPath = ""; @@ -536,20 +533,16 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 925CB7E72C19BC5A00C8F47F /* XCLocalSwiftPackageReference "../.." */ = { + 76DD668F2E5DE75400963397 /* XCLocalSwiftPackageReference "../../../spine-runtimes" */ = { isa = XCLocalSwiftPackageReference; - relativePath = ../..; + relativePath = "../../../spine-runtimes"; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 925CB7E82C19BC5A00C8F47F /* Spine */ = { + 76DD66942E5DE75400963397 /* SpineiOS */ = { isa = XCSwiftPackageProductDependency; - productName = Spine; - }; - 928A8CC12BCFE7DF00D9D35B /* Spine */ = { - isa = XCSwiftPackageProductDependency; - productName = Spine; + productName = SpineiOS; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift b/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift index c8720f30b..1479b9161 100644 --- a/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift +++ b/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import Spine -import SpineCppLite +import SpineiOS +import SpineSwift import SwiftUI struct AnimationStateEvents: View { @@ -38,25 +38,25 @@ struct AnimationStateEvents: View { onInitialized: { controller in controller.skeleton.scaleX = 0.5 controller.skeleton.scaleY = 0.5 - controller.skeleton.findSlot(slotName: "gun")?.setColor(r: 1, g: 0, b: 0, a: 1) + controller.skeleton.findSlot("gun")?.appliedPose.color.set(1, 0, 0, 1) controller.animationStateData.defaultMix = 0.2 - let walk = controller.animationState.setAnimationByName(trackIndex: 0, animationName: "walk", loop: true) - controller.animationStateWrapper.setTrackEntryListener(entry: walk) { type, entry, event in + let walk = controller.animationState.setAnimation(0, "walk", true) + walk.setListener { type, entry, event in print("Walk animation event \(type)") } - controller.animationState.addAnimationByName(trackIndex: 0, animationName: "jump", loop: false, delay: 2) - let run = controller.animationState.addAnimationByName(trackIndex: 0, animationName: "run", loop: true, delay: 0) - controller.animationStateWrapper.setTrackEntryListener(entry: run) { type, entry, event in + controller.animationState.addAnimation(0, "jump", false, 2) + let run = controller.animationState.addAnimation(0, "run", true, 0) + run.setListener { type, entry, event in print("Run animation event \(type)") } - controller.animationStateWrapper.setStateListener { type, entry, event in - if type == SPINE_EVENT_TYPE_EVENT, let event { + controller.animationState.setListener { type, entry, event in + if type == .event, let event { print( "User event: { name: \(event.data.name ?? "--"), intValue: \(event.intValue), floatValue: \(event.floatValue), stringValue: \(event.stringValue ?? "--") }" ) } } - let current = controller.animationState.getCurrent(trackIndex: 0)?.animation.name ?? "--" + let current = controller.animationState.getCurrent(0)?.animation.name ?? "--" print("Current: \(current)") } ) diff --git a/spine-ios/Example/Spine iOS Example/DebugRendering.swift b/spine-ios/Example/Spine iOS Example/DebugRendering.swift index f9cefd6de..b3262099f 100644 --- a/spine-ios/Example/Spine iOS Example/DebugRendering.swift +++ b/spine-ios/Example/Spine iOS Example/DebugRendering.swift @@ -27,7 +27,7 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import Spine +import SpineiOS import SwiftUI struct DebugRendering: View { @@ -71,18 +71,16 @@ final class DebugRenderingModel: ObservableObject { init() { controller = SpineController( onInitialized: { controller in - controller.animationState.setAnimationByName( - trackIndex: 0, - animationName: "walk", - loop: true - ) + controller.animationState.setAnimation(0, "walk", true) }, onAfterPaint: { [weak self] controller in guard let self else { return } - boneRects = controller.drawable.skeleton.bones.map { bone in + let bones = controller.drawable.skeleton.bones + boneRects = (0.. BoneRect? in + guard let bone = bones[i] else { return nil } let position = controller.fromSkeletonCoordinates( - position: CGPointMake(CGFloat(bone.worldX), CGFloat(bone.worldY)) + position: CGPointMake(CGFloat(bone.appliedPose.worldX), CGFloat(bone.appliedPose.worldY)) ) return BoneRect( id: UUID(), diff --git a/spine-ios/Example/Spine iOS Example/DisableRendering.swift b/spine-ios/Example/Spine iOS Example/DisableRendering.swift index 63c20b5c2..01065261e 100644 --- a/spine-ios/Example/Spine iOS Example/DisableRendering.swift +++ b/spine-ios/Example/Spine iOS Example/DisableRendering.swift @@ -27,7 +27,7 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import Spine +import SpineiOS import SwiftUI struct DisableRendering: View { @@ -35,11 +35,7 @@ struct DisableRendering: View { @StateObject var controller = SpineController( onInitialized: { controller in - controller.animationState.setAnimationByName( - trackIndex: 0, - animationName: "walk", - loop: true - ) + controller.animationState.setAnimation(0, "walk", true) } ) diff --git a/spine-ios/Example/Spine iOS Example/DressUp.swift b/spine-ios/Example/Spine iOS Example/DressUp.swift index 7fe0f66a2..2e2f14311 100644 --- a/spine-ios/Example/Spine iOS Example/DressUp.swift +++ b/spine-ios/Example/Spine iOS Example/DressUp.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import Spine -import SpineCppLite +import SpineiOS +import SpineSwift import SwiftUI struct DressUp: View { @@ -94,11 +94,7 @@ final class DressUpModel: ObservableObject { init() { controller = SpineController( onInitialized: { controller in - controller.animationState.setAnimationByName( - trackIndex: 0, - animationName: "dance", - loop: true - ) + controller.animationState.setAnimation(0, "dance", true) }, disposeDrawableOnDeInit: false ) @@ -108,21 +104,22 @@ final class DressUpModel: ObservableObject { skeletonFileName: "mix-and-match-pro.skel" ) try await MainActor.run { - for skin in drawable.skeletonData.skins { + let skins = drawable.skeletonData.skins + for i in 0.. Void - -/// Wrapper class around ``AnimationState``. Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies -/// multiple animations on top of each other (layering). -/// -/// See [Applying Animations](http://esotericsoftware.com/spine-applying-animations/) in the Spine Runtimes Guide. -@objc(SpineAnimationStateWrapper) -@objcMembers -public final class AnimationStateWrapper: NSObject { - - public let animationState: AnimationState - public let aninationStateEvents: AnimationStateEvents - - private var trackEntryListeners = [spine_track_entry: AnimationStateListener]() - - private var stateListener: AnimationStateListener? - - public init(animationState: AnimationState, aninationStateEvents: AnimationStateEvents) { - self.animationState = animationState - self.aninationStateEvents = aninationStateEvents - super.init() - } - - /// The listener for events generated by the provided ``TrackEntry``, or nil. - /// - /// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation - /// for the track, so the track entry listener will not be called for ``EventType`` `SPINE_EVENT_TYPE_START`. - public func setTrackEntryListener(entry: TrackEntry, listener: AnimationStateListener?) { - if let listener { - trackEntryListeners[entry.wrappee] = listener - } else { - trackEntryListeners.removeValue(forKey: entry.wrappee) - } - } - - /// Increments each track entry ``TrackEntry/trackTime``, setting queued animations as current if needed. - public func update(delta: Float) { - animationState.update(delta: delta) - - let numEvents = spine_animation_state_events_get_num_events(aninationStateEvents.wrappee) - for i in 0.. CGRect { let data = drawable.skeletonData let oldSkin: Skin? = drawable.skeleton.skin - let customSkin = Skin.create(name: "custom-skin") + let customSkin = Skin("custom-skin") // Use constructor directly for skinName in skins { - let skin = data.findSkin(name: skinName) - if let skin = data.findSkin(name: skinName) { - customSkin.addSkin(other: skin) + if let skin = data.findSkin(skinName) { + customSkin.addSkin(skin) } } - drawable.skeleton.skin = customSkin - drawable.skeleton.setToSetupPose() + drawable.skeleton.setSkin2(customSkin) + drawable.skeleton.setupPose() - let animation = animation.flatMap { data.findAnimation(name: $0) } + let animation = animation.flatMap { data.findAnimation($0) } var minX = Float.Magnitude.greatestFiniteMagnitude var minY = Float.Magnitude.greatestFiniteMagnitude var maxX = -Float.Magnitude.greatestFiniteMagnitude var maxY = -Float.Magnitude.greatestFiniteMagnitude if let animation { - drawable.animationState.setAnimation(trackIndex: 0, animation: animation, loop: false) + _ = drawable.animationState.setAnimation2(0, animation, false) let steps = Int(max(Double(animation.duration) / stepTime, 1.0)) for i in 0.. 0 ? Float(stepTime) : 0.0) @@ -135,13 +135,13 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider { maxX = minX + bounds.width maxY = minY + bounds.height } - drawable.skeleton.setSkinByName(skinName: "default") + drawable.skeleton.setSkin("default") drawable.animationState.clearTracks() if let oldSkin { - drawable.skeleton.skin = oldSkin + drawable.skeleton.setSkin2(oldSkin) } - drawable.skeleton.setToSetupPose() + drawable.skeleton.setupPose() drawable.update(delta: 0) customSkin.dispose() return CGRectMake(CGFloat(minX), CGFloat(minY), CGFloat(maxX - minX), CGFloat(maxY - minY)) @@ -150,7 +150,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider { /// How a view should be inscribed into another view. @objc -public enum ContentMode: Int { +public enum SpineContentMode: Int { case fit /// As large as possible while still containing the source view entirely within the target view. case fill/// Fill the target view by distorting the source's aspect ratio. @@ -158,7 +158,7 @@ public enum ContentMode: Int { /// How a view should aligned withing another view. @objc -public enum Alignment: Int { +public enum SpineAlignment: Int { case topLeft case topCenter case topRight diff --git a/spine-ios/Sources/SpineiOS/Extensions/RenderCommand+Vertices.swift b/spine-ios/Sources/SpineiOS/Extensions/RenderCommand+Vertices.swift index 9230ad005..16efb04f1 100644 --- a/spine-ios/Sources/SpineiOS/Extensions/RenderCommand+Vertices.swift +++ b/spine-ios/Sources/SpineiOS/Extensions/RenderCommand+Vertices.swift @@ -28,6 +28,7 @@ *****************************************************************************/ import Foundation +import SpineSwift import SpineShadersStructs import simd @@ -35,19 +36,26 @@ extension RenderCommand { func getVertices() -> [SpineVertex] { var vertices = [SpineVertex]() - let indices = indices - let numVertices = numVertices - let positions = positions(numVertices: numVertices) - let uvs = uvs(numVertices: numVertices) - let colors = colors(numVertices: numVertices) - vertices.reserveCapacity(indices.count) - for i in 0..(positions[xIndex], positions[yIndex]) - let uv = SIMD2(uvs[xIndex], uvs[yIndex]) - let color = extractRGBA(from: colors[index]) + let position = SIMD2(positionsArray[xIndex], positionsArray[yIndex]) + let uv = SIMD2(uvsArray[xIndex], uvsArray[yIndex]) + let color = extractRGBA(from: colorsArray[index]) let vertex = SpineVertex( position: position, color: color, @@ -59,10 +67,7 @@ extension RenderCommand { return vertices } - private func extractRGBA(from color: Int32) -> SIMD4 { - guard color != -1 else { - return SIMD4(1.0, 1.0, 1.0, 1.0) - } + private func extractRGBA(from color: UInt32) -> SIMD4 { let alpha = Float((color >> 24) & 0xFF) / 255.0 let red = Float((color >> 16) & 0xFF) / 255.0 let green = Float((color >> 8) & 0xFF) / 255.0 diff --git a/spine-ios/Sources/SpineiOS/Metal/SpineRenderer.swift b/spine-ios/Sources/SpineiOS/Metal/SpineRenderer.swift index a0ffe689a..edbd945a7 100644 --- a/spine-ios/Sources/SpineiOS/Metal/SpineRenderer.swift +++ b/spine-ios/Sources/SpineiOS/Metal/SpineRenderer.swift @@ -31,6 +31,7 @@ import Foundation import MetalKit import SpineSwift import SpineC +import SpineShadersStructs protocol SpineRendererDelegate: AnyObject { func spineRendererWillUpdate(_ spineRenderer: SpineRenderer) @@ -108,11 +109,11 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { ) } - let blendModes = [ - SPINE_BLEND_MODE_NORMAL, - SPINE_BLEND_MODE_ADDITIVE, - SPINE_BLEND_MODE_MULTIPLY, - SPINE_BLEND_MODE_SCREEN, + let blendModes: [BlendMode] = [ + .normal, + .additive, + .multiply, + .screen, ] for blendMode in blendModes { let descriptor = MTLRenderPipelineDescriptor() @@ -185,7 +186,7 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { } } - private func setTransform(bounds: CGRect, mode: Spine.ContentMode, alignment: Spine.Alignment) { + private func setTransform(bounds: CGRect, mode: SpineContentMode, alignment: SpineAlignment) { let x = -bounds.minX - bounds.width / 2.0 let y = -bounds.minY - bounds.height / 2.0 @@ -290,7 +291,8 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { let vertices = allVertices[index] - let textureIndex = Int(renderCommand.atlasPage) + // When using spine_atlas_load, texture is actually the atlas page index cast as a pointer + let textureIndex = Int(bitPattern: renderCommand.texture) if textures.indices.contains(textureIndex) { renderEncoder.setFragmentTexture( textures[textureIndex], @@ -321,63 +323,55 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { extension BlendMode { fileprivate func sourceRGBBlendFactor(premultipliedAlpha: Bool) -> MTLBlendFactor { switch self { - case SPINE_BLEND_MODE_NORMAL: + case .normal: return premultipliedAlpha ? .one : .sourceAlpha - case SPINE_BLEND_MODE_ADDITIVE: + case .additive: // additvie only needs sourceAlpha multiply if it is not pma return premultipliedAlpha ? .one : .sourceAlpha - case SPINE_BLEND_MODE_MULTIPLY: + case .multiply: return .destinationColor - case SPINE_BLEND_MODE_SCREEN: + case .screen: return .one - default: - return .one // Should never be called } } fileprivate var sourceAlphaBlendFactor: MTLBlendFactor { // pma and non-pma has no-relation ship with alpha blending switch self { - case SPINE_BLEND_MODE_NORMAL: + case .normal: return .one - case SPINE_BLEND_MODE_ADDITIVE: + case .additive: return .one - case SPINE_BLEND_MODE_MULTIPLY: + case .multiply: return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_SCREEN: + case .screen: return .oneMinusSourceColor - default: - return .one // Should never be called } } fileprivate var destinationRGBBlendFactor: MTLBlendFactor { switch self { - case SPINE_BLEND_MODE_NORMAL: + case .normal: return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_ADDITIVE: + case .additive: return .one - case SPINE_BLEND_MODE_MULTIPLY: + case .multiply: return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_SCREEN: + case .screen: return .oneMinusSourceColor - default: - return .one // Should never be called } } fileprivate var destinationAlphaBlendFactor: MTLBlendFactor { switch self { - case SPINE_BLEND_MODE_NORMAL: + case .normal: return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_ADDITIVE: + case .additive: return .one - case SPINE_BLEND_MODE_MULTIPLY: + case .multiply: return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_SCREEN: + case .screen: return .oneMinusSourceColor - default: - return .one // Should never be called } } } diff --git a/spine-ios/Sources/SpineiOS/SkeletonDrawableWrapper.swift b/spine-ios/Sources/SpineiOS/SkeletonDrawableWrapper.swift index b6b189561..bf4d45bce 100644 --- a/spine-ios/Sources/SpineiOS/SkeletonDrawableWrapper.swift +++ b/spine-ios/Sources/SpineiOS/SkeletonDrawableWrapper.swift @@ -62,7 +62,6 @@ public final class SkeletonDrawableWrapper: NSObject { public let skeleton: Skeleton public let animationStateData: AnimationStateData public let animationState: AnimationState - public let animationStateWrapper: AnimationStateWrapper internal var disposed = false @@ -121,30 +120,12 @@ public final class SkeletonDrawableWrapper: NSObject { self.atlasPages = atlasPages self.skeletonData = skeletonData - guard let nativeSkeletonDrawable = spine_skeleton_drawable_create(skeletonData.wrappee) else { - throw SpineError("Could not load native skeleton drawable") - } - skeletonDrawable = SkeletonDrawable(nativeSkeletonDrawable) + skeletonDrawable = SkeletonDrawable(skeletonData: skeletonData) - guard let nativeSkeleton = spine_skeleton_drawable_get_skeleton(skeletonDrawable.wrappee) else { - throw SpineError("Could not load native skeleton") - } - skeleton = Skeleton(nativeSkeleton) - - guard let nativeAnimationStateData = spine_skeleton_drawable_get_animation_state_data(skeletonDrawable.wrappee) else { - throw SpineError("Could not load native animation state data") - } - animationStateData = AnimationStateData(nativeAnimationStateData) - - guard let nativeAnimationState = spine_skeleton_drawable_get_animation_state(skeletonDrawable.wrappee) else { - throw SpineError("Could not load native animation state") - } - animationState = AnimationState(nativeAnimationState) - animationStateWrapper = AnimationStateWrapper( - animationState: animationState, - aninationStateEvents: skeletonDrawable.animationStateEvents - ) - skeleton.updateWorldTransform(physics: SPINE_PHYSICS_NONE) + skeleton = skeletonDrawable.skeleton + animationStateData = skeletonDrawable.animationStateData + animationState = skeletonDrawable.animationState + skeleton.updateWorldTransform(Physics.none) super.init() } @@ -154,11 +135,11 @@ public final class SkeletonDrawableWrapper: NSObject { public func update(delta: Float) { if disposed { return } - animationStateWrapper.update(delta: delta) - animationState.apply(skeleton: skeleton) + animationState.update(delta) + _ = animationState.apply(skeleton) - skeleton.update(delta: delta) - skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE) + skeleton.update(delta) + skeleton.updateWorldTransform(Physics.update) } public func dispose() { @@ -168,6 +149,6 @@ public final class SkeletonDrawableWrapper: NSObject { atlas.dispose() skeletonData.dispose() - skeletonDrawable.dispose() + // SkeletonDrawable disposal handled by ARC } } diff --git a/spine-ios/Sources/SpineiOS/SpineController.swift b/spine-ios/Sources/SpineiOS/SpineController.swift index c1081c158..46d9f970e 100644 --- a/spine-ios/Sources/SpineiOS/SpineController.swift +++ b/spine-ios/Sources/SpineiOS/SpineController.swift @@ -31,6 +31,7 @@ import CoreGraphics import Foundation import QuartzCore import UIKit +import SpineSwift public typealias SpineControllerCallback = (_ controller: SpineController) -> Void @@ -136,10 +137,6 @@ public final class SpineController: NSObject, ObservableObject { drawable.animationState } - /// The ``AnimationStateWrapper`` used to hold ``AnimationState``, register ``AnimationStateListener`` and call ``AnimationStateWrapper/update(delta:)`` - public var animationStateWrapper: AnimationStateWrapper { - drawable.animationStateWrapper - } /// Transforms the coordinates given in the ``SpineUIView`` coordinate system in `position` to /// the skeleton coordinate system. See the `IKFollowing.swift` example how to use this @@ -239,6 +236,14 @@ extension SpineController: SpineRendererDataSource { } func renderCommands(_ spineRenderer: SpineRenderer) -> [RenderCommand] { - return drawable?.skeletonDrawable.render() ?? [] + guard let drawable = drawable else { return [] } + + var commands = [RenderCommand]() + var current = drawable.skeletonDrawable.render() + while let cmd = current { + commands.append(cmd) + current = cmd.next + } + return commands } } diff --git a/spine-ios/Sources/SpineiOS/Spine.Generated+Extensions.swift b/spine-ios/Sources/SpineiOS/SpineSwiftExtensions.swift similarity index 62% rename from spine-ios/Sources/SpineiOS/Spine.Generated+Extensions.swift rename to spine-ios/Sources/SpineiOS/SpineSwiftExtensions.swift index 4229cbace..bdeb750da 100644 --- a/spine-ios/Sources/SpineiOS/Spine.Generated+Extensions.swift +++ b/spine-ios/Sources/SpineiOS/SpineSwiftExtensions.swift @@ -30,24 +30,26 @@ import Foundation import SpineSwift import SwiftUI +import UIKit +// Re-export version info from SpineSwift public var version: String { - return "\(majorVersion).\(minorVersion)" + return SpineSwift.version } public var majorVersion: Int { - return Int(spine_major_version()) + return SpineSwift.majorVersion } public var minorVersion: Int { - return Int(spine_minor_version()) + return SpineSwift.minorVersion } /// ``Atlas`` data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image, /// a corresponding `UIImage` is constructed, which is used when rendering a skeleton /// that uses this atlas. /// -/// Use the static methods ``Atlas/fromBundle(_:bundle:)``, ``Atlas/fromFile(_:)``, and ``Atlas/fromHttp(_:)`` to load an atlas. Call ``Atlas/dispose()` +/// Use the static methods ``Atlas/fromBundle(_:bundle:)``, ``Atlas/fromFile(_:)``, and ``Atlas/fromHttp(_:)`` to load an atlas. Call ``Atlas/dispose()`` /// when the atlas is no longer in use to release its resources. extension Atlas { @@ -89,34 +91,26 @@ extension Atlas { guard let atlasData = String(data: data, encoding: .utf8) else { throw SpineError("Couldn't read atlas bytes as utf8 string") } - let atlas = try atlasData.utf8CString.withUnsafeBufferPointer { - guard let atlas = spine_atlas_load($0.baseAddress) else { - throw SpineError("Couldn't load atlas data") - } - return atlas - } - if let error = spine_atlas_get_error(atlas) { - let message = String(cString: error) - spine_atlas_dispose(atlas) - throw SpineError("Couldn't load atlas: \(message)") - } - + + // Use SpineSwift's loadAtlas function + let atlas = try loadAtlas(atlasData) + var atlasPages = [UIImage]() - let numImagePaths = spine_atlas_get_num_image_paths(atlas) - - for i in 0.. SkeletonData { - let result = try data.withUnsafeBytes { - try $0.withMemoryRebound(to: UInt8.self) { buffer in - guard let ptr = buffer.baseAddress else { - throw SpineError("Couldn't read atlas binary") - } - return spine_skeleton_data_load_binary( - atlas.wrappee, - ptr, - Int32(buffer.count) - ) - } - } - guard let result else { - throw SpineError("Couldn't load skeleton data") - } - defer { - spine_skeleton_data_result_dispose(result) - } - if let error = spine_skeleton_data_result_get_error(result) { - let message = String(cString: error) - throw SpineError("Couldn't load skeleton data: \(message)") - } - guard let data = spine_skeleton_data_result_get_data(result) else { - throw SpineError("Couldn't load skeleton data from result") - } - return SkeletonData(data) - } - - /// Loads a ``SkeletonData`` from the `json` string, using the provided ``Atlas`` to resolve attachment - /// images. - /// - /// Throws an `Error` in case the atlas could not be loaded. - public static func fromJson(atlas: Atlas, json: String) throws -> SkeletonData { - let result = try json.utf8CString.withUnsafeBufferPointer { buffer in - guard - let basePtr = buffer.baseAddress, - let result = spine_skeleton_data_load_json(atlas.wrappee, basePtr) - else { - throw SpineError("Couldn't load skeleton data json") - } - return result - } - defer { - spine_skeleton_data_result_dispose(result) - } - if let error = spine_skeleton_data_result_get_error(result) { - let message = String(cString: error) - throw SpineError("Couldn't load skeleton data: \(message)") - } - guard let data = spine_skeleton_data_result_get_data(result) else { - throw SpineError("Couldn't load skeleton data from result") - } - return SkeletonData(data) - } - - private static func fromData(atlas: Atlas, data: Data, isJson: Bool) throws -> SkeletonData { + private static func fromData(atlas: Atlas, data: Data, isJson: Bool, path: String) throws -> SkeletonData { if isJson { guard let json = String(data: data, encoding: .utf8) else { throw SpineError("Couldn't read skeleton data json string") } - return try fromJson(atlas: atlas, json: json) + return try loadSkeletonDataJson(atlas: atlas, jsonData: json, path: path) } else { - return try fromData(atlas: atlas, data: data) + return try loadSkeletonDataBinary(atlas: atlas, binaryData: data, path: path) } } } -extension SkeletonDrawable { - - func render() -> [RenderCommand] { - var commands = [RenderCommand]() - if disposed { return commands } - - var nativeCmd = spine_skeleton_drawable_render(wrappee) - repeat { - if let ncmd = nativeCmd { - commands.append(RenderCommand(ncmd)) - nativeCmd = spine_render_command_get_next(ncmd) - } else { - nativeCmd = nil - } - } while nativeCmd != nil - - return commands - } -} - -extension RenderCommand { - - var numVertices: Int { - Int(spine_render_command_get_num_vertices(wrappee)) - } - - func positions(numVertices: Int) -> [Float] { - let num = numVertices * 2 - let ptr = spine_render_command_get_positions(wrappee) - guard let validPtr = ptr else { return [] } - let buffer = UnsafeBufferPointer(start: validPtr, count: num) - return Array(buffer) - } - - func uvs(numVertices: Int) -> [Float] { - let num = numVertices * 2 - let ptr = spine_render_command_get_uvs(wrappee) - guard let validPtr = ptr else { return [] } - let buffer = UnsafeBufferPointer(start: validPtr, count: num) - return Array(buffer) - } - - func colors(numVertices: Int) -> [Int32] { - let num = numVertices - let ptr = spine_render_command_get_colors(wrappee) - guard let validPtr = ptr else { return [] } - let buffer = UnsafeBufferPointer(start: validPtr, count: num) - return Array(buffer) - } -} - -extension Skin { - - /// Constructs a new empty ``Skin`` using the given `name`. Skins constructed this way must be manually disposed via the `dispose` method - /// if they are no longer used. - public static func create(name: String) -> Skin { - return Skin(spine_skin_create(name)) - } -} - // Helper extension CGRect { @@ -380,26 +258,5 @@ internal enum FileSource { } } -public struct SpineError: Error, CustomStringConvertible { - - public let description: String - - internal init(_ description: String) { - self.description = description - } - -} - -extension SkeletonBounds { - public static func create() -> SkeletonBounds { - return SkeletonBounds(spine_skeleton_bounds_create()) - } -} - -@objc extension Atlas { - - public var imagePathCount: Int32 { - spine_atlas_get_num_image_paths(wrappee) - } - -} +// Re-export SpineError from SpineSwift +public typealias SpineError = SpineSwift.SpineError \ No newline at end of file diff --git a/spine-ios/Sources/SpineiOS/SpineUIView.swift b/spine-ios/Sources/SpineiOS/SpineUIView.swift index 93b26878a..302562ba8 100644 --- a/spine-ios/Sources/SpineiOS/SpineUIView.swift +++ b/spine-ios/Sources/SpineiOS/SpineUIView.swift @@ -29,6 +29,7 @@ import MetalKit import UIKit +import SpineSwift /// A ``UIView`` to display a Spine skeleton. The skeleton can be loaded from a bundle, local files, http, or a pre-loaded ``SkeletonDrawableWrapper``. /// @@ -42,8 +43,8 @@ import UIKit public final class SpineUIView: MTKView { let controller: SpineController - let mode: Spine.ContentMode - let alignment: Spine.Alignment + let mode: SpineContentMode + let alignment: SpineAlignment let boundsProvider: BoundsProvider internal var computedBounds: CGRect = .zero @@ -51,8 +52,8 @@ public final class SpineUIView: MTKView { @objc internal init( controller: SpineController = SpineController(), - mode: Spine.ContentMode = .fit, - alignment: Spine.Alignment = .center, + mode: SpineContentMode = .fit, + alignment: SpineAlignment = .center, boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { @@ -83,8 +84,8 @@ public final class SpineUIView: MTKView { public convenience init( from source: SpineViewSource, controller: SpineController = SpineController(), - mode: Spine.ContentMode = .fit, - alignment: Spine.Alignment = .center, + mode: SpineContentMode = .fit, + alignment: SpineAlignment = .center, boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { @@ -120,8 +121,8 @@ public final class SpineUIView: MTKView { skeletonFileName: String, bundle: Bundle = .main, controller: SpineController = SpineController(), - mode: Spine.ContentMode = .fit, - alignment: Spine.Alignment = .center, + mode: SpineContentMode = .fit, + alignment: SpineAlignment = .center, boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { @@ -149,8 +150,8 @@ public final class SpineUIView: MTKView { atlasFile: URL, skeletonFile: URL, controller: SpineController = SpineController(), - mode: Spine.ContentMode = .fit, - alignment: Spine.Alignment = .center, + mode: SpineContentMode = .fit, + alignment: SpineAlignment = .center, boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { @@ -178,8 +179,8 @@ public final class SpineUIView: MTKView { atlasURL: URL, skeletonURL: URL, controller: SpineController = SpineController(), - mode: Spine.ContentMode = .fit, - alignment: Spine.Alignment = .center, + mode: SpineContentMode = .fit, + alignment: SpineAlignment = .center, boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { @@ -204,8 +205,8 @@ public final class SpineUIView: MTKView { @objc public convenience init( drawable: SkeletonDrawableWrapper, controller: SpineController = SpineController(), - mode: Spine.ContentMode = .fit, - alignment: Spine.Alignment = .center, + mode: SpineContentMode = .fit, + alignment: SpineAlignment = .center, boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { @@ -251,7 +252,7 @@ extension SpineUIView { commandQueue: SpineObjects.shared.commandQueue, pixelFormat: colorPixelFormat, atlasPages: atlasPages, - pma: controller.drawable.atlas.isPma + pma: false // TODO: Get PMA flag from atlas when API is available ) renderer?.delegate = controller renderer?.dataSource = controller diff --git a/spine-ios/Sources/SpineiOS/SpineView.swift b/spine-ios/Sources/SpineiOS/SpineView.swift index ec79fe38d..68e444954 100644 --- a/spine-ios/Sources/SpineiOS/SpineView.swift +++ b/spine-ios/Sources/SpineiOS/SpineView.swift @@ -43,8 +43,8 @@ public struct SpineView: UIViewRepresentable { private let source: SpineViewSource private let controller: SpineController - private let mode: Spine.ContentMode - private let alignment: Spine.Alignment + private let mode: SpineContentMode + private let alignment: SpineAlignment private let boundsProvider: BoundsProvider private let backgroundColor: UIColor // Not using `SwiftUI.Color`, as briging to `UIColor` prior iOS 14 might not always work. @@ -71,8 +71,8 @@ public struct SpineView: UIViewRepresentable { public init( from source: SpineViewSource, controller: SpineController = SpineController(), - mode: Spine.ContentMode = .fit, - alignment: Spine.Alignment = .center, + mode: SpineContentMode = .fit, + alignment: SpineAlignment = .center, boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear, isRendering: Binding = .constant(nil)