From 2ba91231f3abe1554f510dd602fdd3722f1b0c4e Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Mon, 16 Mar 2026 18:10:00 +0100 Subject: [PATCH] [haxe] Port latest libgdx timeline, sequence, draw order, and follow-up fixes. See #2989. --- .../{HasTextureRegion.hx => HasSequence.hx} | 12 +- spine-haxe/spine-haxe/spine/Sequence.hx | 100 +++++++-- spine-haxe/spine-haxe/spine/Skeleton.hx | 3 +- spine-haxe/spine-haxe/spine/SkeletonBinary.hx | 135 ++++++------ spine-haxe/spine-haxe/spine/SkeletonData.hx | 15 ++ spine-haxe/spine-haxe/spine/SkeletonJson.hx | 128 +++++++----- spine-haxe/spine-haxe/spine/Skin.hx | 2 +- .../spine/animation/AlphaTimeline.hx | 24 ++- .../spine/animation/AnimationState.hx | 15 +- .../animation/DrawOrderFolderTimeline.hx | 126 +++++++++++ .../spine/animation/DrawOrderTimeline.hx | 2 +- .../spine/animation/IkConstraintTimeline.hx | 36 ++-- .../animation/PathConstraintMixTimeline.hx | 23 +-- .../PathConstraintSpacingTimeline.hx | 2 +- .../PhysicsConstraintGravityTimeline.hx | 1 + .../animation/PhysicsConstraintTimeline.hx | 3 + .../PhysicsConstraintWindTimeline.hx | 1 + .../spine/animation/RGB2Timeline.hx | 137 ++++++------ .../spine/animation/RGBA2Timeline.hx | 123 +++++------ .../spine/animation/RGBATimeline.hx | 13 +- .../spine-haxe/spine/animation/RGBTimeline.hx | 82 ++++---- .../spine/animation/SequenceTimeline.hx | 24 +-- .../attachments/AtlasAttachmentLoader.hx | 66 ++---- .../spine/attachments/Attachment.hx | 5 + .../spine/attachments/MeshAttachment.hx | 195 +++++++++--------- .../spine/attachments/RegionAttachment.hx | 188 ++++++++--------- .../spine/attachments/VertexAttachment.hx | 5 - .../spine-haxe/spine/flixel/SkeletonSprite.hx | 15 +- .../spine/starling/SkeletonSprite.hx | 20 +- 29 files changed, 858 insertions(+), 643 deletions(-) rename spine-haxe/spine-haxe/spine/{HasTextureRegion.hx => HasSequence.hx} (80%) create mode 100644 spine-haxe/spine-haxe/spine/animation/DrawOrderFolderTimeline.hx diff --git a/spine-haxe/spine-haxe/spine/HasTextureRegion.hx b/spine-haxe/spine-haxe/spine/HasSequence.hx similarity index 80% rename from spine-haxe/spine-haxe/spine/HasTextureRegion.hx rename to spine-haxe/spine-haxe/spine/HasSequence.hx index 9a714de48..8c69a4d82 100644 --- a/spine-haxe/spine-haxe/spine/HasTextureRegion.hx +++ b/spine-haxe/spine-haxe/spine/HasSequence.hx @@ -29,20 +29,14 @@ package spine; -interface HasTextureRegion { - /** The name used to find the region. */ +interface HasSequence { public var path:String; - /** Sets the region used to draw the attachment. After setting the region or if the region's properties are changed, - * updateRegion() must be called. */ - public var region:TextureRegion; - /** The color to tint the attachment. */ public var color:Color; public var sequence:Sequence; - /** Updates any values the attachment calculates using the region. Must be called after setting the - * region or if the region's properties are changed. */ - public function updateRegion():Void; + /** Calls Sequence.update() on this attachment's sequence. */ + public function updateSequence():Void; } diff --git a/spine-haxe/spine-haxe/spine/Sequence.hx b/spine-haxe/spine-haxe/spine/Sequence.hx index d31d9037c..95dba7e9d 100644 --- a/spine-haxe/spine-haxe/spine/Sequence.hx +++ b/spine-haxe/spine-haxe/spine/Sequence.hx @@ -29,7 +29,11 @@ package spine; -/** A sequence for an attachment with multiple texture regions, which can be used for animation. */ +import spine.attachments.MeshAttachment; +import spine.attachments.RegionAttachment; + +/** Holds texture regions, UVs, and vertex offsets for rendering a region or mesh attachment. Regions must + * be populated and update() called before use. */ class Sequence { private static var _nextID = 0; @@ -37,49 +41,109 @@ class Sequence { public var id = _nextID++; public var regions:Array; + public var pathSuffix:Bool; + public var uvs:Array>; + public var offsets:Array>; public var start = 0; public var digits = 0; /** The index of the region to show for the setup pose. */ public var setupIndex = 0; - public function new(count:Int) { + public function new(count:Int, pathSuffix:Bool) { this.regions = new Array(); this.regions.resize(count); + this.pathSuffix = pathSuffix; } /** Copy constructor. */ public function copy():Sequence { - var copy = new Sequence(this.regions.length); - for (i in 0...this.regions.length) { + var regionCount = this.regions.length; + var copy = new Sequence(regionCount, this.pathSuffix); + for (i in 0...regionCount) copy.regions[i] = this.regions[i]; - } copy.start = this.start; copy.digits = this.digits; copy.setupIndex = this.setupIndex; + + if (this.uvs != null) { + var length = this.uvs[0].length; + copy.uvs = new Array>(); + copy.uvs.resize(regionCount); + for (i in 0...regionCount) { + copy.uvs[i] = new Array(); + copy.uvs[i].resize(length); + for (j in 0...length) + copy.uvs[i][j] = this.uvs[i][j]; + } + } + if (this.offsets != null) { + copy.offsets = new Array>(); + copy.offsets.resize(regionCount); + for (i in 0...regionCount) { + copy.offsets[i] = new Array(); + copy.offsets[i].resize(8); + for (j in 0...8) + copy.offsets[i][j] = this.offsets[i][j]; + } + } + return copy; } - public function apply(slot:SlotPose, attachment:HasTextureRegion) { - var index:Int = slot.sequenceIndex; - if (index == -1) - index = this.setupIndex; - if (index >= this.regions.length) - index = this.regions.length - 1; - var region = this.regions[index]; - if (attachment.region != region) { - attachment.region = region; - attachment.updateRegion(); + /** Computes UVs and offsets for the specified attachment. Must be called if the regions or attachment properties are + * changed. */ + public function update(attachment:HasSequence):Void { + var regionCount = this.regions.length; + if (Std.isOfType(attachment, RegionAttachment)) { + var region:RegionAttachment = cast(attachment, RegionAttachment); + this.uvs = new Array>(); + this.uvs.resize(regionCount); + this.offsets = new Array>(); + this.offsets.resize(regionCount); + for (i in 0...regionCount) { + this.uvs[i] = new Array(); + this.uvs[i].resize(8); + this.offsets[i] = new Array(); + this.offsets[i].resize(8); + RegionAttachment.computeUVs(this.regions[i], region.x, region.y, region.scaleX, region.scaleY, region.rotation, + region.width, region.height, this.offsets[i], this.uvs[i]); + } + } else if (Std.isOfType(attachment, MeshAttachment)) { + var mesh:MeshAttachment = cast(attachment, MeshAttachment); + var regionUVs = mesh.regionUVs; + this.uvs = new Array>(); + this.uvs.resize(regionCount); + this.offsets = null; + for (i in 0...regionCount) { + this.uvs[i] = new Array(); + this.uvs[i].resize(regionUVs.length); + MeshAttachment.computeUVs(this.regions[i], regionUVs, this.uvs[i]); + } } } + public function resolveIndex(pose:SlotPose):Int { + var index:Int = pose.sequenceIndex; + if (index == -1) index = this.setupIndex; + if (index >= this.regions.length) index = this.regions.length - 1; + return index; + } + + public function getUVs(index:Int):Array { + return this.uvs[index]; + } + + public function getHasPathSuffix():Bool { + return this.pathSuffix; + } + public function getPath(basePath:String, index:Int):String { + if (!this.pathSuffix) return basePath; var result = basePath; var frame = Std.string(this.start + index); - - for (i in 0...(this.digits - frame.length)) { + for (i in 0...(this.digits - frame.length)) result += "0"; - } result += frame; return result; } diff --git a/spine-haxe/spine-haxe/spine/Skeleton.hx b/spine-haxe/spine-haxe/spine/Skeleton.hx index 01cc1b7df..4a94405d8 100644 --- a/spine-haxe/spine-haxe/spine/Skeleton.hx +++ b/spine-haxe/spine-haxe/spine/Skeleton.hx @@ -443,7 +443,8 @@ class Skeleton { verticesLength = 8; _tempVertices.resize(verticesLength); vertices = _tempVertices; - cast(attachment, RegionAttachment).computeWorldVertices(slot, vertices, 0, 2); + var region:RegionAttachment = cast(attachment, RegionAttachment); + region.computeWorldVertices(slot, region.getOffsets(slot.applied), vertices, 0, 2); triangles = Skeleton.quadTriangles; } else if (Std.isOfType(attachment, MeshAttachment)) { var mesh:MeshAttachment = cast(attachment, MeshAttachment); diff --git a/spine-haxe/spine-haxe/spine/SkeletonBinary.hx b/spine-haxe/spine-haxe/spine/SkeletonBinary.hx index 47e8b6b90..2d0f461d2 100644 --- a/spine-haxe/spine-haxe/spine/SkeletonBinary.hx +++ b/spine-haxe/spine-haxe/spine/SkeletonBinary.hx @@ -41,6 +41,7 @@ import spine.animation.BoneTimeline2; import spine.animation.CurveTimeline1; import spine.animation.CurveTimeline; import spine.animation.DeformTimeline; +import spine.animation.DrawOrderFolderTimeline; import spine.animation.DrawOrderTimeline; import spine.animation.EventTimeline; import spine.animation.IkConstraintTimeline; @@ -349,7 +350,7 @@ class SkeletonBinary { data.slot = slots[input.readInt(true)]; var flags = input.readByte(); data.skinRequired = (flags & 1) != 0; - data.positionMode = PositionMode.values[(flags >> 1) & 2]; + data.positionMode = PositionMode.values[(flags >> 1) & 1]; data.spacingMode = SpacingMode.values[(flags >> 2) & 3]; data.rotateMode = RotateMode.values[(flags >> 4) & 3]; if ((flags & 128) != 0) @@ -467,8 +468,7 @@ class SkeletonBinary { throw new SpineException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? cast(parent, VertexAttachment) : linkedMesh.mesh; linkedMesh.mesh.parentMesh = cast(parent, MeshAttachment); - if (linkedMesh.mesh.region != null) - linkedMesh.mesh.updateRegion(); + linkedMesh.mesh.updateSequence(); } linkedMeshes.resize(0); @@ -546,8 +546,10 @@ class SkeletonBinary { return skin; } - private function readSequence(input:BinaryInput):Sequence { - var sequence = new Sequence(input.readInt(true)); + private function readSequence(input:BinaryInput, hasPathSuffix:Bool):Sequence { + if (!hasPathSuffix) + return new Sequence(1, false); + var sequence = new Sequence(input.readInt(true), true); sequence.start = input.readInt(true); sequence.digits = input.readInt(true); sequence.setupIndex = input.readInt(true); @@ -574,7 +576,7 @@ class SkeletonBinary { case AttachmentType.region: path = (flags & 16) != 0 ? input.readStringRef() : null; color = (flags & 32) != 0 ? input.readInt32() : 0xffffffff; - var sequence = (flags & 64) != 0 ? readSequence(input) : null; + var sequence = readSequence(input, (flags & 64) != 0); rotation = (flags & 128) != 0 ? input.readFloat() : 0; x = input.readFloat(); y = input.readFloat(); @@ -597,9 +599,7 @@ class SkeletonBinary { region.width = width * scale; region.height = height * scale; region.color.setFromRgba8888(color); - region.sequence = sequence; - if (region.region != null) - region.updateRegion(); + region.updateSequence(); return region; case AttachmentType.boundingbox: vertices = readVertices(input, (flags & 16) != 0); @@ -618,7 +618,7 @@ class SkeletonBinary { case AttachmentType.mesh: path = (flags & 16) != 0 ? input.readStringRef() : name; color = (flags & 32) != 0 ? input.readInt32() : 0xffffffff; - var sequence = (flags & 64) != 0 ? readSequence(input) : null; + var sequence = readSequence(input, (flags & 64) != 0); var hullLength = input.readInt(true); vertices = readVertices(input, (flags & 128) != 0); var uvs:Array = readFloatArray(input, vertices.length, 1); @@ -637,28 +637,26 @@ class SkeletonBinary { return null; mesh.path = path; mesh.color.setFromRgba8888(color); + mesh.hullLength = hullLength << 1; if (vertices.bones.length > 0) mesh.bones = vertices.bones; mesh.vertices = vertices.vertices; mesh.worldVerticesLength = vertices.length; - mesh.triangles = triangles; mesh.regionUVs = uvs; - if (mesh.region != null) - mesh.updateRegion(); - mesh.hullLength = hullLength << 1; - mesh.sequence = sequence; + mesh.triangles = triangles; if (nonessential) { mesh.edges = edges; mesh.width = width * scale; mesh.height = height * scale; } + mesh.updateSequence(); return mesh; case AttachmentType.linkedmesh: path = (flags & 16) != 0 ? input.readStringRef() : name; if (path == null) throw new SpineException("Path of linked mesh must not be null"); color = (flags & 32) != 0 ? input.readInt32() : 0xffffffff; - var sequence = (flags & 64) != 0 ? this.readSequence(input) : null; + var sequence = readSequence(input, (flags & 64) != 0); var inheritTimelines:Bool = (flags & 128) != 0; var skinIndex = input.readInt(true); var parent:String = input.readStringRef(); @@ -672,7 +670,6 @@ class SkeletonBinary { return null; mesh.path = path; mesh.color.setFromRgba8888(color); - mesh.sequence = sequence; if (nonessential) { mesh.width = width * scale; mesh.height = height * scale; @@ -746,23 +743,54 @@ class SkeletonBinary { vertices.vertices = readFloatArray(input, vertices.length, scale); return vertices; } + var n:Int = input.readInt(true); + var bones:Array = new Array(); var weights:Array = new Array(); - var bonesArray:Array = new Array(); - for (i in 0...vertexCount) { + var b:Int = 0, w:Int = 0; + while (b < n) { var boneCount:Int = input.readInt(true); - bonesArray.push(boneCount); + bones[b++] = boneCount; for (ii in 0...boneCount) { - bonesArray.push(input.readInt(true)); - weights.push(input.readFloat() * scale); - weights.push(input.readFloat() * scale); - weights.push(input.readFloat()); + bones[b++] = input.readInt(true); + weights[w] = input.readFloat() * scale; + weights[w + 1] = input.readFloat() * scale; + weights[w + 2] = input.readFloat(); + w += 3; } } vertices.vertices = weights; - vertices.bones = bonesArray; + vertices.bones = bones; return vertices; } + private function readDrawOrder(input:BinaryInput, slotCount:Int):Array { + var changeCount:Int = input.readInt(true); + if (changeCount == 0) + return null; + var drawOrder:Array = new Array(); + drawOrder.resize(slotCount); + for (i in 0...slotCount) + drawOrder[i] = -1; + var unchanged:Array = new Array(); + unchanged.resize(slotCount - changeCount); + var originalIndex:Int = 0, unchangedIndex:Int = 0; + for (i in 0...changeCount) { + var slotIndex:Int = input.readInt(true); + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + drawOrder[originalIndex + input.readInt(true)] = originalIndex++; + } + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + var i:Int = slotCount - 1; + while (i >= 0) { + if (drawOrder[i] == -1) + drawOrder[i] = unchanged[--unchangedIndex]; + i--; + } + return drawOrder; + } + private function readFloatArray(input:BinaryInput, n:Int, scale:Float):Array { var array:Array = new Array(); if (scale == 1) { @@ -1337,7 +1365,7 @@ class SkeletonBinary { } timelines.push(deformTimeline); case ATTACHMENT_SEQUENCE: - var timeline = new SequenceTimeline(frameCount, slotIndex, cast(attachment, HasTextureRegion)); + var timeline = new SequenceTimeline(frameCount, slotIndex, cast(attachment, HasSequence)); for (frame in 0...frameCount) { var time = input.readFloat(); var modeAndIndex = input.readInt32(); @@ -1350,48 +1378,31 @@ class SkeletonBinary { } } - // Draw order timelines. + // Draw order timeline. + var slotCount:Int = skeletonData.slots.length; var drawOrderCount:Int = input.readInt(true); if (drawOrderCount > 0) { var drawOrderTimeline:DrawOrderTimeline = new DrawOrderTimeline(drawOrderCount); - var slotCount:Int = skeletonData.slots.length; - for (i in 0...drawOrderCount) { - time = input.readFloat(); - var offsetCount:Int = input.readInt(true); - var drawOrder:Array = new Array(); - drawOrder.resize(slotCount); - var ii:Int = slotCount - 1; - while (ii >= 0) { - drawOrder[ii--] = -1; - } - var unchanged:Array = new Array(); - unchanged.resize(slotCount - offsetCount); - var originalIndex:Int = 0, unchangedIndex:Int = 0; - for (ii in 0...offsetCount) { - slotIndex = input.readInt(true); - // Collect unchanged items. - while (originalIndex != slotIndex) { - unchanged[unchangedIndex++] = originalIndex++; - } - // Set changed items. - drawOrder[originalIndex + input.readInt(true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) { - unchanged[unchangedIndex++] = originalIndex++; - } - // Fill in unchanged items. - ii = slotCount - 1; - while (ii >= 0) { - if (drawOrder[ii] == -1) - drawOrder[ii] = unchanged[--unchangedIndex]; - ii--; - } - drawOrderTimeline.setFrame(i, time, drawOrder); - } + for (i in 0...drawOrderCount) + drawOrderTimeline.setFrame(i, input.readFloat(), readDrawOrder(input, slotCount)); timelines.push(drawOrderTimeline); } + // Draw order folder timelines. + var folderCount:Int = input.readInt(true); + for (i in 0...folderCount) { + var folderSlotCount:Int = input.readInt(true); + var folderSlots:Array = new Array(); + folderSlots.resize(folderSlotCount); + for (ii in 0...folderSlotCount) + folderSlots[ii] = input.readInt(true); + var keyCount:Int = input.readInt(true); + var folderTimeline = new DrawOrderFolderTimeline(keyCount, folderSlots, slotCount); + for (ii in 0...keyCount) + folderTimeline.setFrame(ii, input.readFloat(), readDrawOrder(input, folderSlotCount)); + timelines.push(folderTimeline); + } + // Event timelines. var eventCount:Int = input.readInt(true); if (eventCount > 0) { diff --git a/spine-haxe/spine-haxe/spine/SkeletonData.hx b/spine-haxe/spine-haxe/spine/SkeletonData.hx index a18d4c5af..b28a11dbd 100644 --- a/spine-haxe/spine-haxe/spine/SkeletonData.hx +++ b/spine-haxe/spine-haxe/spine/SkeletonData.hx @@ -33,6 +33,7 @@ import haxe.io.Bytes; import spine.animation.Animation; import spine.atlas.TextureAtlas; import spine.attachments.AtlasAttachmentLoader; +import spine.SliderData; /** Stores the setup pose and all of the stateless data for a skeleton. * @@ -191,6 +192,20 @@ class SkeletonData { // --- Animations. + /** Collects animations used by slider constraints. + * Slider animations are designed to be applied by slider constraints rather than on their own. Applications that have a user + * choose an animation may want to exclude them. */ + public function findSliderAnimations(animations:Array):Array { + for (constraint in constraints) { + if (Std.isOfType(constraint, SliderData)) { + var data:SliderData = cast(constraint, SliderData); + if (data.animation != null) + animations.push(data.animation); + } + } + return animations; + } + /** Finds an animation by comparing each animation's name. It is more efficient to cache the results of this method than to * call it multiple times. * @param animationName The name of the animation to find. diff --git a/spine-haxe/spine-haxe/spine/SkeletonJson.hx b/spine-haxe/spine-haxe/spine/SkeletonJson.hx index 7c4125978..92c0619ac 100644 --- a/spine-haxe/spine-haxe/spine/SkeletonJson.hx +++ b/spine-haxe/spine-haxe/spine/SkeletonJson.hx @@ -42,6 +42,7 @@ import spine.animation.AttachmentTimeline; import spine.animation.CurveTimeline1; import spine.animation.CurveTimeline; import spine.animation.DeformTimeline; +import spine.animation.DrawOrderFolderTimeline; import spine.animation.DrawOrderTimeline; import spine.animation.EventTimeline; import spine.animation.IkConstraintTimeline; @@ -497,8 +498,7 @@ class SkeletonJson { throw new SpineException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? cast(parentMesh, VertexAttachment) : linkedMesh.mesh; linkedMesh.mesh.parentMesh = cast(parentMesh, MeshAttachment); - if (linkedMesh.mesh.region != null) - linkedMesh.mesh.updateRegion(); + linkedMesh.mesh.updateSequence(); } linkedMeshes.resize(0); @@ -592,14 +592,11 @@ class SkeletonJson { region.rotation = getFloat(map, "rotation"); region.width = getFloat(map, "width") * scale; region.height = getFloat(map, "height") * scale; - region.sequence = sequence; - color = Reflect.getProperty(map, "color"); if (color != null) { region.color.setFromString(color); } - if (region.region != null) - region.updateRegion(); + region.updateSequence(); return region; case AttachmentType.mesh, AttachmentType.linkedmesh: var path = getString(map, "path", name); @@ -616,7 +613,6 @@ class SkeletonJson { mesh.width = getFloat(map, "width") * scale; mesh.height = getFloat(map, "height") * scale; - mesh.sequence = sequence; if (Reflect.field(map, "parent") != null) { var inheritTimelines:Bool = Reflect.hasField(map, "timelines") ? cast(Reflect.field(map, "timelines"), Bool) : true; @@ -628,12 +624,11 @@ class SkeletonJson { readVertices(map, mesh, uvs.length); mesh.triangles = getIntArray(map, "triangles"); mesh.regionUVs = uvs; - if (mesh.region != null) - mesh.updateRegion(); if (Reflect.field(map, "edges") != null) mesh.edges = getIntArray(map, "edges"); mesh.hullLength = getInt(map, "hull") * 2; + mesh.updateSequence(); return mesh; case AttachmentType.boundingbox: var box:BoundingBoxAttachment = attachmentLoader.newBoundingBoxAttachment(skin, name); @@ -689,14 +684,55 @@ class SkeletonJson { private function readSequence(map:Dynamic) { if (map == null) - return null; - var sequence = new Sequence(getInt(map, "count", 0)); + return new Sequence(1, false); + var sequence = new Sequence(getInt(map, "count", 0), true); sequence.start = getInt(map, "start", 1); sequence.digits = getInt(map, "digits", 0); sequence.setupIndex = getInt(map, "setup", 0); return sequence; } + /** @param folderSlots Slot names are resolved to positions within this array. If null, slot indices are used as positions. */ + private function readDrawOrder(skeletonData:SkeletonData, keyMap:Dynamic, slotCount:Int, folderSlots:Array):Array { + var changes:Array = Reflect.getProperty(keyMap, "offsets"); + if (changes == null) return null; + var drawOrder:Array = new Array(); + drawOrder.resize(slotCount); + for (i in 0...slotCount) + drawOrder[i] = -1; + var unchanged:Array = new Array(); + unchanged.resize(slotCount - changes.length); + var originalIndex:Int = 0, unchangedIndex:Int = 0; + for (offsetMap in changes) { + var slot = skeletonData.findSlot(Reflect.getProperty(offsetMap, "slot")); + if (slot == null) throw new SpineException("Draw order slot not found: " + Reflect.getProperty(offsetMap, "slot")); + var index:Int; + if (folderSlots == null) + index = slot.index; + else { + index = -1; + for (i in 0...slotCount) { + if (folderSlots[i] == slot.index) { + index = i; + break; + } + } + if (index == -1) throw new SpineException("Slot not in folder: " + Reflect.getProperty(offsetMap, "slot")); + } + while (originalIndex != index) + unchanged[unchangedIndex++] = originalIndex++; + drawOrder[originalIndex + Reflect.getProperty(offsetMap, "offset")] = originalIndex++; + } + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + var i:Int = slotCount - 1; + while (i >= 0) { + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + i--; + } + return drawOrder; + } + private function readVertices(map:Dynamic, attachment:VertexAttachment, verticesLength:Int):Void { attachment.worldVerticesLength = verticesLength; var vertices:Array = getFloatArray(map, "vertices"); @@ -1222,7 +1258,7 @@ class SkeletonJson { slotMap = Reflect.field(attachmentsMap, slotMapName); slotIndex = skeletonData.findSlot(slotMapName).index; if (slotIndex == -1) - throw new SpineException("Slot not found: " + slotMapName); + throw new SpineException("Attachment slot not found: " + slotMapName); for (attachmentMapName in Reflect.fields(slotMap)) { var attachmentMap = Reflect.field(slotMap, attachmentMapName); var attachment:Attachment = skin.getAttachment(slotIndex, attachmentMapName); @@ -1295,7 +1331,7 @@ class SkeletonJson { timelines.push(deformTimeline); case "sequence": - var timeline = new SequenceTimeline(timelineMap.length, slotIndex, cast(attachment, HasTextureRegion)); + var timeline = new SequenceTimeline(timelineMap.length, slotIndex, cast(attachment, HasSequence)); var lastDelay:Float = 0; var frame:Int = 0; while (frame < timelineMap.length) { @@ -1316,54 +1352,44 @@ class SkeletonJson { } // Draw order timelines. + // Draw order timeline. if (Reflect.hasField(map, "drawOrder")) { var drawOrders:Array = cast(Reflect.field(map, "drawOrder"), Array); if (drawOrders != null) { var drawOrderTimeline:DrawOrderTimeline = new DrawOrderTimeline(drawOrders.length); var slotCount:Int = skeletonData.slots.length; frame = 0; - for (drawOrderMap in drawOrders) { - var drawOrder:Array = null; - var offsets:Array = Reflect.getProperty(drawOrderMap, "offsets"); - if (offsets != null) { - drawOrder = new Array(); - drawOrder.resize(slotCount); - var i = slotCount - 1; - while (i >= 0) { - drawOrder[i--] = -1; - } - var unchanged:Array = new Array(); - unchanged.resize(slotCount - offsets.length); - var originalIndex:Int = 0, unchangedIndex:Int = 0; - for (offsetMap in offsets) { - slotIndex = skeletonData.findSlot(Reflect.getProperty(offsetMap, "slot")).index; - if (slotIndex == -1) - throw new SpineException("Slot not found: " + Reflect.getProperty(offsetMap, "slot")); - // Collect unchanged items. - while (originalIndex != slotIndex) { - unchanged[unchangedIndex++] = originalIndex++; - } - // Set changed items. - drawOrder[originalIndex + Reflect.getProperty(offsetMap, "offset")] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) { - unchanged[unchangedIndex++] = originalIndex++; - } - // Fill in unchanged items. - i = slotCount - 1; - while (i >= 0) { - if (drawOrder[i] == -1) - drawOrder[i] = unchanged[--unchangedIndex]; - i--; - } - } - drawOrderTimeline.setFrame(frame++, getFloat(drawOrderMap, "time"), drawOrder); - } + for (drawOrderMap in drawOrders) + drawOrderTimeline.setFrame(frame++, getFloat(drawOrderMap, "time"), readDrawOrder(skeletonData, drawOrderMap, slotCount, null)); timelines.push(drawOrderTimeline); } } + // Draw order folder timelines. + if (Reflect.hasField(map, "drawOrderFolder")) { + var drawOrderFolders:Array = cast(Reflect.field(map, "drawOrderFolder"), Array); + if (drawOrderFolders != null) { + for (timelineMap in drawOrderFolders) { + var slotEntries:Array = cast(Reflect.field(timelineMap, "slots"), Array); + var folderSlots:Array = new Array(); + folderSlots.resize(slotEntries.length); + var ii:Int = 0; + for (slotEntry in slotEntries) { + var slot = skeletonData.findSlot(cast(slotEntry, String)); + if (slot == null) throw new SpineException("Draw order folder slot not found: " + cast(slotEntry, String)); + folderSlots[ii++] = slot.index; + } + + var keys:Array = cast(Reflect.field(timelineMap, "keys"), Array); + var folderTimeline = new DrawOrderFolderTimeline(keys.length, folderSlots, skeletonData.slots.length); + frame = 0; + for (keyMap in keys) + folderTimeline.setFrame(frame++, getFloat(keyMap, "time"), readDrawOrder(skeletonData, keyMap, folderSlots.length, folderSlots)); + timelines.push(folderTimeline); + } + } + } + // Event timelines. if (Reflect.hasField(map, "events")) { var eventsMap:Array = cast(Reflect.field(map, "events"), Array); diff --git a/spine-haxe/spine-haxe/spine/Skin.hx b/spine-haxe/spine-haxe/spine/Skin.hx index 852351bd0..3336300bf 100644 --- a/spine-haxe/spine-haxe/spine/Skin.hx +++ b/spine-haxe/spine-haxe/spine/Skin.hx @@ -142,7 +142,7 @@ class Skin { continue; if (Std.isOfType(attachment.attachment, MeshAttachment)) { var mesh = cast(attachment.attachment, MeshAttachment); - attachment.attachment = new MeshAttachment(mesh.name, mesh.path).newLinkedMesh(); + attachment.attachment = mesh.newLinkedMesh(); setAttachment(attachment.slotIndex, attachment.name, attachment.attachment); } else { attachment.attachment = attachment.attachment.copy(); diff --git a/spine-haxe/spine-haxe/spine/animation/AlphaTimeline.hx b/spine-haxe/spine-haxe/spine/animation/AlphaTimeline.hx index f1dac1675..aecfe134d 100644 --- a/spine-haxe/spine-haxe/spine/animation/AlphaTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/AlphaTimeline.hx @@ -62,24 +62,28 @@ class AlphaTimeline extends CurveTimeline1 implements SlotTimeline { return; var color = (appliedPose ? slot.applied : slot.pose).color; + var a:Float = 0; if (time < frames[0]) { var setup:Color = slot.data.setup.color; switch (blend) { case MixBlend.setup: color.a = setup.a; + return; case MixBlend.first: - color.a += (setup.a - color.a) * alpha; + a = color.a + (setup.a - color.a) * alpha; + default: + return; } - return; - } - - var a:Float = getCurveValue(time); - if (alpha == 1) { - color.a = a; } else { - if (blend == MixBlend.setup) - color.a = slot.data.setup.color.a; - color.a += (a - color.a) * alpha; + a = getCurveValue(time); + if (alpha != 1) { + if (blend == MixBlend.setup) { + var setup = slot.data.setup.color; + a = setup.a + (a - setup.a) * alpha; + } else + a = color.a + (a - color.a) * alpha; + } } + color.a = a < 0 ? 0 : (a > 1 ? 1 : a); } } diff --git a/spine-haxe/spine-haxe/spine/animation/AnimationState.hx b/spine-haxe/spine-haxe/spine/animation/AnimationState.hx index bc12cdb6b..2f0109098 100644 --- a/spine-haxe/spine-haxe/spine/animation/AnimationState.hx +++ b/spine-haxe/spine-haxe/spine/animation/AnimationState.hx @@ -266,7 +266,7 @@ class AnimationState { attachments = true; for (timeline in timelines) { if (Std.isOfType(timeline, AttachmentTimeline)) { - applyAttachmentTimeline(cast(timeline, AttachmentTimeline), skeleton, applyTime, blend, attachments); + applyAttachmentTimeline(cast(timeline, AttachmentTimeline), skeleton, applyTime, blend, false, attachments); } else { timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.mixIn, false); } @@ -286,7 +286,7 @@ class AnimationState { this.applyRotateTimeline(cast(timeline, RotateTimeline), skeleton, applyTime, alpha, timelineBlend, current.timelinesRotation, ii << 1, firstFrame); } else if (Std.isOfType(timeline, AttachmentTimeline)) { - this.applyAttachmentTimeline(cast(timeline, AttachmentTimeline), skeleton, applyTime, blend, attachments); + this.applyAttachmentTimeline(cast(timeline, AttachmentTimeline), skeleton, applyTime, blend, false, attachments); } else { timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.mixIn, false); } @@ -366,7 +366,6 @@ class AnimationState { from.totalAlpha = 0; for (i in 0...timelineCount) { var timeline:Timeline = timelines[i]; - var direction:MixDirection = MixDirection.mixOut; var timelineBlend:MixBlend; var alpha:Float = 0; switch (timelineMode[i]) { @@ -395,8 +394,9 @@ class AnimationState { applyRotateTimeline(cast(timeline, RotateTimeline), skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame); } else if (Std.isOfType(timeline, AttachmentTimeline)) { applyAttachmentTimeline(cast(timeline, AttachmentTimeline), skeleton, applyTime, - timelineBlend, attachments && alpha >= from.alphaAttachmentThreshold); + timelineBlend, true, attachments && alpha >= from.alphaAttachmentThreshold); } else { + var direction = MixDirection.mixOut; if (drawOrder && Std.isOfType(timeline, DrawOrderTimeline) && timelineBlend == MixBlend.setup) direction = MixDirection.mixIn; timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false); @@ -419,12 +419,14 @@ class AnimationState { * is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent * timelines see any deform. */ - public function applyAttachmentTimeline(timeline:AttachmentTimeline, skeleton:Skeleton, time:Float, blend:MixBlend, attachments:Bool) { + public function applyAttachmentTimeline(timeline:AttachmentTimeline, skeleton:Skeleton, time:Float, blend:MixBlend, out:Bool, attachments:Bool) { var slot = skeleton.slots[timeline.slotIndex]; if (!slot.bone.active) return; - if (time < timeline.frames[0]) { // Time is before first frame. + if (out) { + if (blend == MixBlend.setup) this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments); + } else if (time < timeline.frames[0]) { // Time is before first frame. if (blend == MixBlend.setup || blend == MixBlend.first) this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments); } else @@ -889,6 +891,7 @@ class AnimationState { } else if (to == null || Std.isOfType(timeline, AttachmentTimeline) || Std.isOfType(timeline, DrawOrderTimeline) + || Std.isOfType(timeline, DrawOrderFolderTimeline) || Std.isOfType(timeline, EventTimeline) || !to.animation.hasTimeline(ids)) { timelineMode[i] = FIRST; diff --git a/spine-haxe/spine-haxe/spine/animation/DrawOrderFolderTimeline.hx b/spine-haxe/spine-haxe/spine/animation/DrawOrderFolderTimeline.hx new file mode 100644 index 000000000..f71546f03 --- /dev/null +++ b/spine-haxe/spine-haxe/spine/animation/DrawOrderFolderTimeline.hx @@ -0,0 +1,126 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +package spine.animation; + +import spine.Event; +import spine.Skeleton; +import spine.Slot; + +/** Changes a subset of a skeleton's spine.Skeleton.drawOrder. */ +class DrawOrderFolderTimeline extends Timeline { + private var slots:Array; + private var inFolder:Array; + private var drawOrders:Array>; + + /** @param slots spine.Skeleton.slots indices controlled by this timeline, in setup order. + * @param slotCount The maximum number of slots in the skeleton. */ + public function new(frameCount:Int, slots:Array, slotCount:Int) { + super(frameCount, Property.drawOrder); + this.slots = slots; + drawOrders = new Array>(); + drawOrders.resize(frameCount); + inFolder = new Array(); + inFolder.resize(slotCount); + for (i in 0...slotCount) + inFolder[i] = false; + for (i in slots) + inFolder[i] = true; + } + + public var frameCount(get, never):Int; + + private function get_frameCount():Int { + return frames.length; + } + + /** The spine.Skeleton.slots indices that this timeline affects, in setup order. */ + public function getSlots():Array { + return slots; + } + + /** The draw order for each frame. See setFrame(). */ + public function getDrawOrders():Array> { + return drawOrders; + } + + /** Sets the time and draw order for the specified frame. + * @param frame Between 0 and frameCount, inclusive. + * @param time The frame time in seconds. + * @param drawOrder Ordered getSlots() indices, or null to use setup pose order. */ + public function setFrame(frame:Int, time:Float, drawOrder:Array):Void { + frames[frame] = time; + drawOrders[frame] = drawOrder; + } + + public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array, alpha:Float, blend:MixBlend, direction:MixDirection, + appliedPose:Bool) { + if (direction == MixDirection.mixOut) { + if (blend == MixBlend.setup) setupApply(skeleton); + } else if (time < frames[0]) { + if (blend == MixBlend.setup || blend == MixBlend.first) setupApply(skeleton); + } else { + var order = drawOrders[Timeline.search1(frames, time)]; + if (order == null) + setupApply(skeleton); + else + orderApply(skeleton, order); + } + } + + private function setupApply(skeleton:Skeleton):Void { + var drawOrder = skeleton.drawOrder; + var allSlots = skeleton.slots; + var found = 0, done = slots.length; + var i = 0; + while (true) { + if (inFolder[drawOrder[i].data.index]) { + drawOrder[i] = allSlots[slots[found]]; + found++; + if (found == done) break; + } + i++; + } + } + + private function orderApply(skeleton:Skeleton, order:Array):Void { + var drawOrder = skeleton.drawOrder; + var allSlots = skeleton.slots; + var found = 0, done = slots.length; + var i = 0; + while (true) { + if (inFolder[drawOrder[i].data.index]) { + drawOrder[i] = allSlots[slots[order[found]]]; + found++; + if (found == done) break; + } + i++; + } + } +} diff --git a/spine-haxe/spine-haxe/spine/animation/DrawOrderTimeline.hx b/spine-haxe/spine-haxe/spine/animation/DrawOrderTimeline.hx index c7cc519d1..dfa335d1e 100644 --- a/spine-haxe/spine-haxe/spine/animation/DrawOrderTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/DrawOrderTimeline.hx @@ -53,7 +53,7 @@ class DrawOrderTimeline extends Timeline { /** Sets the time and draw order for the specified frame. * @param frame Between 0 and frameCount, inclusive. * @param time The frame time in seconds. - * @param drawOrder For each slot in spine.Skeleton.slots, the index of the slot in the new draw order. May be null to use setup pose draw order. */ + * @param drawOrder Ordered spine.Skeleton.slots indices, or null to use setup pose order. */ public function setFrame(frame:Int, time:Float, drawOrder:Array):Void { frames[frame] = time; drawOrders[frame] = drawOrder; diff --git a/spine-haxe/spine-haxe/spine/animation/IkConstraintTimeline.hx b/spine-haxe/spine-haxe/spine/animation/IkConstraintTimeline.hx index 90d366756..b3d8829fd 100644 --- a/spine-haxe/spine-haxe/spine/animation/IkConstraintTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/IkConstraintTimeline.hx @@ -115,27 +115,21 @@ class IkConstraintTimeline extends CurveTimeline implements ConstraintTimeline { softness = getBezierValue(time, i, SOFTNESS, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER); } - switch (blend) { - case MixBlend.setup: - var setup = constraint.data.setup; - pose.mix = setup.mix + (mix - setup.mix) * alpha; - pose.softness = setup.softness + (softness - setup.softness) * alpha; - if (direction == MixDirection.mixOut) { - pose.bendDirection = setup.bendDirection; - pose.compress = setup.compress; - pose.stretch = setup.stretch; - return; - } - case MixBlend.first, MixBlend.replace: - pose.mix += (mix - pose.mix) * alpha; - pose.softness += (softness - pose.softness) * alpha; - if (direction == MixDirection.mixOut) - return; - case MixBlend.add: - pose.mix += mix * alpha; - pose.softness += softness * alpha; - if (direction == MixDirection.mixOut) - return; + if (blend == MixBlend.setup) { + var setup = constraint.data.setup; + pose.mix = setup.mix + (mix - setup.mix) * alpha; + pose.softness = setup.softness + (softness - setup.softness) * alpha; + if (direction == MixDirection.mixOut) { + pose.bendDirection = setup.bendDirection; + pose.compress = setup.compress; + pose.stretch = setup.stretch; + return; + } + } else { + pose.mix += (mix - pose.mix) * alpha; + pose.softness += (softness - pose.softness) * alpha; + if (direction == MixDirection.mixOut) + return; } pose.bendDirection = Std.int(frames[i + BEND_DIRECTION]); pose.compress = frames[i + COMPRESS] != 0; diff --git a/spine-haxe/spine-haxe/spine/animation/PathConstraintMixTimeline.hx b/spine-haxe/spine-haxe/spine/animation/PathConstraintMixTimeline.hx index 9fb7e6646..511da61a3 100644 --- a/spine-haxe/spine-haxe/spine/animation/PathConstraintMixTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/PathConstraintMixTimeline.hx @@ -114,20 +114,15 @@ class PathConstraintMixTimeline extends CurveTimeline implements ConstraintTimel y = getBezierValue(time, i, Y, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER); } - switch (blend) { - case MixBlend.setup: - var setup = constraint.data.setup; - pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha; - pose.mixX = setup.mixX + (x - setup.mixX) * alpha; - pose.mixY = setup.mixY + (y - setup.mixY) * alpha; - case MixBlend.first, MixBlend.replace: - pose.mixRotate += (rotate - pose.mixRotate) * alpha; - pose.mixX += (x - pose.mixX) * alpha; - pose.mixY += (y - pose.mixY) * alpha; - case MixBlend.add: - pose.mixRotate += rotate * alpha; - pose.mixX += x * alpha; - pose.mixY += y * alpha; + if (blend == MixBlend.setup) { + var setup = constraint.data.setup; + pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha; + pose.mixX = setup.mixX + (x - setup.mixX) * alpha; + pose.mixY = setup.mixY + (y - setup.mixY) * alpha; + } else { + pose.mixRotate += (rotate - pose.mixRotate) * alpha; + pose.mixX += (x - pose.mixX) * alpha; + pose.mixY += (y - pose.mixY) * alpha; } } } diff --git a/spine-haxe/spine-haxe/spine/animation/PathConstraintSpacingTimeline.hx b/spine-haxe/spine-haxe/spine/animation/PathConstraintSpacingTimeline.hx index d089b3f15..997010732 100644 --- a/spine-haxe/spine-haxe/spine/animation/PathConstraintSpacingTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/PathConstraintSpacingTimeline.hx @@ -44,7 +44,7 @@ class PathConstraintSpacingTimeline extends ConstraintTimeline1 { var constraint = cast(skeleton.constraints[constraintIndex], PathConstraint); if (constraint.active) { var pose = appliedPose ? constraint.applied : constraint.pose; - pose.spacing = getAbsoluteValue(time, alpha, blend, pose.spacing, constraint.data.setup.spacing); + pose.spacing = getAbsoluteValue(time, alpha, blend == MixBlend.add ? MixBlend.replace : blend, pose.spacing, constraint.data.setup.spacing); } } } diff --git a/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintGravityTimeline.hx b/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintGravityTimeline.hx index df3362be1..de6e98e7e 100644 --- a/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintGravityTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintGravityTimeline.hx @@ -33,6 +33,7 @@ package spine.animation; class PhysicsConstraintGravityTimeline extends PhysicsConstraintTimeline { public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) { super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintGravity); + this.additive = true; } public function get(pose:PhysicsConstraintPose):Float { diff --git a/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintTimeline.hx b/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintTimeline.hx index 01801adf8..8b8bcd6c9 100644 --- a/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintTimeline.hx @@ -35,6 +35,8 @@ import spine.Skeleton; /** The base class for most spine.PhysicsConstraint timelines. */ abstract class PhysicsConstraintTimeline extends ConstraintTimeline1 { + public var additive:Bool = false; + /** * @param constraintIndex -1 for all physics constraints in the skeleton. */ @@ -44,6 +46,7 @@ abstract class PhysicsConstraintTimeline extends ConstraintTimeline1 { public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array, alpha:Float, blend:MixBlend, direction:MixDirection, appliedPose:Bool) { + if (blend == MixBlend.add && !additive) blend = MixBlend.replace; if (constraintIndex == -1) { var value:Float = time >= frames[0] ? getCurveValue(time) : 0; for (constraint in skeleton.physics) { diff --git a/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintWindTimeline.hx b/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintWindTimeline.hx index a3cd7e117..b56c0aab3 100644 --- a/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintWindTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/PhysicsConstraintWindTimeline.hx @@ -33,6 +33,7 @@ package spine.animation; class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline { public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) { super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintWind); + this.additive = true; } public function get(pose:PhysicsConstraintPose):Float { diff --git a/spine-haxe/spine-haxe/spine/animation/RGB2Timeline.hx b/spine-haxe/spine-haxe/spine/animation/RGB2Timeline.hx index b79b692d5..0b5171c0d 100644 --- a/spine-haxe/spine-haxe/spine/animation/RGB2Timeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/RGB2Timeline.hx @@ -63,9 +63,10 @@ class RGB2Timeline extends SlotCurveTimeline { public function apply1(slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) { var light:Color = pose.color, dark:Color = pose.darkColor; + var r:Float = 0, g:Float = 0, b:Float = 0, r2:Float = 0, g2:Float = 0, b2:Float = 0; if (time < frames[0]) { var setup = slot.data.setup; - var setupLight = setup.color, setupDark = setup.darkColor; + var setupLight:Color = setup.color, setupDark:Color = setup.darkColor; switch (blend) { case MixBlend.setup: light.r = setupLight.r; @@ -74,76 +75,78 @@ class RGB2Timeline extends SlotCurveTimeline { dark.r = setupDark.r; dark.g = setupDark.g; dark.b = setupDark.b; + return; case MixBlend.first: - light.r += (setupLight.r - light.r) * alpha; - light.g += (setupLight.g - light.g) * alpha; - light.b += (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; + r = light.r + (setupLight.r - light.r) * alpha; + g = light.g + (setupLight.g - light.g) * alpha; + b = light.b + (setupLight.b - light.b) * alpha; + r2 = dark.r + (setupDark.r - dark.r) * alpha; + g2 = dark.g + (setupDark.g - dark.g) * alpha; + b2 = dark.b + (setupDark.b - dark.b) * alpha; + default: + return; } - return; - } - - var r:Float = 0, g:Float = 0, b:Float = 0, a:Float = 0, r2:Float = 0, g2:Float = 0, b2:Float = 0; - var i:Int = Timeline.search(frames, time, ENTRIES); - var curveType:Int = Std.int(curves[Std.int(i / ENTRIES)]); - switch (curveType) { - case CurveTimeline.LINEAR: - var before:Float = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - var t:Float = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - case CurveTimeline.STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - default: - r = getBezierValue(time, i, R, curveType - CurveTimeline.BEZIER); - g = getBezierValue(time, i, G, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER); - b = getBezierValue(time, i, B, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER); - r2 = getBezierValue(time, i, R2, curveType + CurveTimeline.BEZIER_SIZE * 3 - CurveTimeline.BEZIER); - g2 = getBezierValue(time, i, G2, curveType + CurveTimeline.BEZIER_SIZE * 4 - CurveTimeline.BEZIER); - b2 = getBezierValue(time, i, B2, curveType + CurveTimeline.BEZIER_SIZE * 5 - CurveTimeline.BEZIER); - } - - if (alpha == 1) { - light.r = r; - light.g = g; - light.b = b; - dark.r = r2; - dark.g = g2; - dark.b = b2; } else { - if (blend == MixBlend.setup) { - var setup = slot.data.setup; - var setupLight = setup.color, setupDark = setup.darkColor; - light.r = setupLight.r; - light.g = setupLight.g; - light.b = setupLight.b; - dark.r = setupDark.r; - dark.g = setupDark.g; - dark.b = setupDark.b; + var i:Int = Timeline.search(frames, time, ENTRIES); + var curveType:Int = Std.int(curves[Std.int(i / ENTRIES)]); + switch (curveType) { + case CurveTimeline.LINEAR: + var before:Float = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + var t:Float = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + case CurveTimeline.STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + default: + r = getBezierValue(time, i, R, curveType - CurveTimeline.BEZIER); + g = getBezierValue(time, i, G, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER); + b = getBezierValue(time, i, B, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER); + r2 = getBezierValue(time, i, R2, curveType + CurveTimeline.BEZIER_SIZE * 3 - CurveTimeline.BEZIER); + g2 = getBezierValue(time, i, G2, curveType + CurveTimeline.BEZIER_SIZE * 4 - CurveTimeline.BEZIER); + b2 = getBezierValue(time, i, B2, curveType + CurveTimeline.BEZIER_SIZE * 5 - CurveTimeline.BEZIER); + } + + if (alpha != 1) { + if (blend == MixBlend.setup) { + var setupPose = slot.data.setup; + var setup = setupPose.color; + r = setup.r + (r - setup.r) * alpha; + g = setup.g + (g - setup.g) * alpha; + b = setup.b + (b - setup.b) * alpha; + setup = setupPose.darkColor; + r2 = setup.r + (r2 - setup.r) * alpha; + g2 = setup.g + (g2 - setup.g) * alpha; + b2 = setup.b + (b2 - setup.b) * alpha; + } else { + r = light.r + (r - light.r) * alpha; + g = light.g + (g - light.g) * alpha; + b = light.b + (b - light.b) * alpha; + r2 = dark.r + (r2 - dark.r) * alpha; + g2 = dark.g + (g2 - dark.g) * alpha; + b2 = dark.b + (b2 - dark.b) * alpha; + } } - light.r += (r - light.r) * alpha; - light.g += (g - light.g) * alpha; - light.b += (b - light.b) * alpha; - dark.r += (r2 - dark.r) * alpha; - dark.g += (g2 - dark.g) * alpha; - dark.b += (b2 - dark.b) * alpha; } + light.r = r < 0 ? 0 : (r > 1 ? 1 : r); + light.g = g < 0 ? 0 : (g > 1 ? 1 : g); + light.b = b < 0 ? 0 : (b > 1 ? 1 : b); + dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2); + dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2); + dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2); } } diff --git a/spine-haxe/spine-haxe/spine/animation/RGBA2Timeline.hx b/spine-haxe/spine-haxe/spine/animation/RGBA2Timeline.hx index e1ce8fea2..33db2cab4 100644 --- a/spine-haxe/spine-haxe/spine/animation/RGBA2Timeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/RGBA2Timeline.hx @@ -71,6 +71,7 @@ class RGBA2Timeline extends SlotCurveTimeline { public function apply1(slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) { var light = pose.color, dark = pose.darkColor; + var r2:Float = 0, g2:Float = 0, b2:Float = 0; if (time < frames[0]) { var setup = slot.data.setup; var setupLight = setup.color, setupDark = setup.darkColor; @@ -80,70 +81,76 @@ class RGBA2Timeline extends SlotCurveTimeline { dark.r = setupDark.r; dark.g = setupDark.g; dark.b = 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; + r2 = dark.r + (setupDark.r - dark.r) * alpha; + g2 = dark.g + (setupDark.g - dark.g) * alpha; + b2 = dark.b + (setupDark.b - dark.b) * alpha; + default: + return; } - return; - } - - var r:Float = 0, g:Float = 0, b:Float = 0, a:Float = 0, r2:Float = 0, g2:Float = 0, b2:Float = 0; - var i:Int = Timeline.search(frames, time, ENTRIES); - var curveType:Int = Std.int(curves[i >> 3]); - switch (curveType) { - case CurveTimeline.LINEAR: - var before:Float = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - var t:Float = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - case CurveTimeline.STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - default: - r = getBezierValue(time, i, R, curveType - CurveTimeline.BEZIER); - g = getBezierValue(time, i, G, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER); - b = getBezierValue(time, i, B, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER); - a = getBezierValue(time, i, A, curveType + CurveTimeline.BEZIER_SIZE * 3 - CurveTimeline.BEZIER); - r2 = getBezierValue(time, i, R2, curveType + CurveTimeline.BEZIER_SIZE * 4 - CurveTimeline.BEZIER); - g2 = getBezierValue(time, i, G2, curveType + CurveTimeline.BEZIER_SIZE * 5 - CurveTimeline.BEZIER); - b2 = getBezierValue(time, i, B2, curveType + CurveTimeline.BEZIER_SIZE * 6 - CurveTimeline.BEZIER); - } - - if (alpha == 1) { - light.set(r, g, b, a); - dark.r = r2; - dark.g = g2; - dark.b = b2; } else { - if (blend == MixBlend.setup) { - var setup = slot.data.setup; - light.setFromColor(setup.color); - dark.setFromColor(setup.darkColor); + var r:Float = 0, g:Float = 0, b:Float = 0, a:Float = 0; + var i:Int = Timeline.search(frames, time, ENTRIES); + var curveType:Int = Std.int(curves[i >> 3]); + switch (curveType) { + case CurveTimeline.LINEAR: + var before:Float = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + var t:Float = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + case CurveTimeline.STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + default: + r = getBezierValue(time, i, R, curveType - CurveTimeline.BEZIER); + g = getBezierValue(time, i, G, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER); + b = getBezierValue(time, i, B, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER); + a = getBezierValue(time, i, A, curveType + CurveTimeline.BEZIER_SIZE * 3 - CurveTimeline.BEZIER); + r2 = getBezierValue(time, i, R2, curveType + CurveTimeline.BEZIER_SIZE * 4 - CurveTimeline.BEZIER); + g2 = getBezierValue(time, i, G2, curveType + CurveTimeline.BEZIER_SIZE * 5 - CurveTimeline.BEZIER); + b2 = getBezierValue(time, i, B2, curveType + CurveTimeline.BEZIER_SIZE * 6 - CurveTimeline.BEZIER); + } + + if (alpha == 1) + light.set(r, g, b, a); + else if (blend == MixBlend.setup) { + var setupPose = slot.data.setup; + var setup = setupPose.color; + light.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha, + setup.a + (a - setup.a) * alpha); + setup = setupPose.darkColor; + r2 = setup.r + (r2 - setup.r) * alpha; + g2 = setup.g + (g2 - setup.g) * alpha; + b2 = setup.b + (b2 - setup.b) * alpha; + } else { + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + r2 = dark.r + (r2 - dark.r) * alpha; + g2 = dark.g + (g2 - dark.g) * alpha; + b2 = dark.b + (b2 - dark.b) * alpha; } - 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; } + dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2); + dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2); + dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2); } } diff --git a/spine-haxe/spine-haxe/spine/animation/RGBATimeline.hx b/spine-haxe/spine-haxe/spine/animation/RGBATimeline.hx index 9b65dd6a6..e8248f670 100644 --- a/spine-haxe/spine-haxe/spine/animation/RGBATimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/RGBATimeline.hx @@ -97,12 +97,15 @@ class RGBATimeline extends SlotCurveTimeline { a = getBezierValue(time, i, A, curveType + CurveTimeline.BEZIER_SIZE * 3 - CurveTimeline.BEZIER); } - if (alpha == 1) { + if (alpha == 1) color.set(r, g, b, a); - } else { - if (blend == MixBlend.setup) - color.setFromColor(slot.data.setup.color); - color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + else { + if (blend == MixBlend.setup) { + var setup = slot.data.setup.color; + color.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha, + setup.a + (a - setup.a) * alpha); + } else + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); } } } diff --git a/spine-haxe/spine-haxe/spine/animation/RGBTimeline.hx b/spine-haxe/spine-haxe/spine/animation/RGBTimeline.hx index 84fd65611..a895daf1c 100644 --- a/spine-haxe/spine-haxe/spine/animation/RGBTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/RGBTimeline.hx @@ -57,6 +57,7 @@ class RGBTimeline extends SlotCurveTimeline { public function apply1(slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) { var color = pose.color; + var r:Float = 0, g:Float = 0, b:Float = 0; if (time < frames[0]) { var setup = slot.data.setup.color; switch (blend) { @@ -64,50 +65,51 @@ class RGBTimeline extends SlotCurveTimeline { color.r = setup.r; color.g = setup.g; color.b = setup.b; + return; case MixBlend.first: - color.r += (setup.r - color.r) * alpha; - color.g += (setup.g - color.g) * alpha; - color.b += (setup.b - color.b) * alpha; + r = color.r + (setup.r - color.r) * alpha; + g = color.g + (setup.g - color.g) * alpha; + b = color.b + (setup.b - color.b) * alpha; + default: + return; } - return; - } - - var r:Float = 0, g:Float = 0, b:Float = 0; - var i:Int = Timeline.search(frames, time, ENTRIES); - var curveType:Int = Std.int(curves[Std.int(i / ENTRIES)]); - switch (curveType) { - case CurveTimeline.LINEAR: - var before:Float = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - var t:Float = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - case CurveTimeline.STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - default: - r = getBezierValue(time, i, R, curveType - CurveTimeline.BEZIER); - g = getBezierValue(time, i, G, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER); - b = getBezierValue(time, i, B, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER); - } - if (alpha == 1) { - color.r = r; - color.g = g; - color.b = b; } else { - if (blend == MixBlend.setup) { - var setup = slot.data.setup.color; - color.r = setup.r; - color.g = setup.g; - color.b = setup.b; + var i:Int = Timeline.search(frames, time, ENTRIES); + var curveType:Int = Std.int(curves[Std.int(i / ENTRIES)]); + switch (curveType) { + case CurveTimeline.LINEAR: + var before:Float = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + var t:Float = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + case CurveTimeline.STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + default: + r = getBezierValue(time, i, R, curveType - CurveTimeline.BEZIER); + g = getBezierValue(time, i, G, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER); + b = getBezierValue(time, i, B, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER); + } + if (alpha != 1) { + if (blend == MixBlend.setup) { + var setup = slot.data.setup.color; + r = setup.r + (r - setup.r) * alpha; + g = setup.g + (g - setup.g) * alpha; + b = setup.b + (b - setup.b) * alpha; + } else { + r = color.r + (r - color.r) * alpha; + g = color.g + (g - color.g) * alpha; + b = color.b + (b - color.b) * alpha; + } } - color.r += (r - color.r) * alpha; - color.g += (g - color.g) * alpha; - color.b += (b - color.b) * alpha; } + color.r = r < 0 ? 0 : (r > 1 ? 1 : r); + color.g = g < 0 ? 0 : (g > 1 ? 1 : g); + color.b = b < 0 ? 0 : (b > 1 ? 1 : b); } } diff --git a/spine-haxe/spine-haxe/spine/animation/SequenceTimeline.hx b/spine-haxe/spine-haxe/spine/animation/SequenceTimeline.hx index 3ce3b49ad..9262d77ab 100644 --- a/spine-haxe/spine-haxe/spine/animation/SequenceTimeline.hx +++ b/spine-haxe/spine-haxe/spine/animation/SequenceTimeline.hx @@ -29,7 +29,6 @@ package spine.animation; -import spine.attachments.VertexAttachment; import spine.attachments.Attachment; /** Changes a slot's Slot#getSequenceIndex() for an attachment's Sequence. */ @@ -39,9 +38,9 @@ class SequenceTimeline extends Timeline implements SlotTimeline { static var DELAY = 2; var slotIndex:Int; - var attachment:HasTextureRegion; + var attachment:HasSequence; - public function new(frameCount:Int, slotIndex:Int, attachment:HasTextureRegion) { + public function new(frameCount:Int, slotIndex:Int, attachment:HasSequence) { super(frameCount, Std.string(Property.sequence) + "|" + Std.string(slotIndex) + "|" + Std.string(attachment.sequence.id)); this.slotIndex = slotIndex; this.attachment = attachment; @@ -55,6 +54,8 @@ class SequenceTimeline extends Timeline implements SlotTimeline { return this.slotIndex; } + /** The attachment for which the sequenceIndex will be set. + * See VertexAttachment.timelineAttachment. */ public function getAttachment():Attachment { return cast(attachment, Attachment); } @@ -76,12 +77,11 @@ class SequenceTimeline extends Timeline implements SlotTimeline { return; var pose = appliedPose ? slot.applied : slot.pose; - var slotAttachment = pose.attachment; - var attachment = cast(this.attachment, Attachment); - if (slotAttachment != attachment) { - if (!Std.isOfType(slotAttachment, VertexAttachment) || cast(slotAttachment, VertexAttachment).timelineAttachment != attachment) - return; - } + var slotAttachment:Attachment = cast(pose.attachment, Attachment); + var attachmentRef:Attachment = cast(this.attachment, Attachment); + + if (!Std.isOfType(slotAttachment, HasSequence) || slotAttachment.timelineAttachment != attachmentRef) + return; if (direction == MixDirection.mixOut) { if (blend == MixBlend.setup) @@ -100,10 +100,8 @@ class SequenceTimeline extends Timeline implements SlotTimeline { var modeAndIndex = Std.int(frames[i + SequenceTimeline.MODE]); var delay = frames[i + SequenceTimeline.DELAY]; - if (this.attachment.sequence == null) - return; - var index = modeAndIndex >> 4, - count = this.attachment.sequence.regions.length; + var hasSeq:HasSequence = cast(slotAttachment, HasSequence); + var index = modeAndIndex >> 4, count = hasSeq.sequence.regions.length; var mode = SequenceMode.values[modeAndIndex & 0xf]; if (mode != SequenceMode.hold) { index += Std.int(((time - before) / delay + 0.00001)); diff --git a/spine-haxe/spine-haxe/spine/attachments/AtlasAttachmentLoader.hx b/spine-haxe/spine-haxe/spine/attachments/AtlasAttachmentLoader.hx index 1bcb058d1..3f7d9310c 100644 --- a/spine-haxe/spine-haxe/spine/attachments/AtlasAttachmentLoader.hx +++ b/spine-haxe/spine-haxe/spine/attachments/AtlasAttachmentLoader.hx @@ -30,13 +30,10 @@ package spine.attachments; import spine.atlas.TextureAtlas; +import spine.atlas.TextureAtlasRegion; import spine.Skin; +import spine.Sequence; -/** - * The interface which can be implemented to customize creating and populating attachments. - * - * @see https://esotericsoftware.com/spine-loading-skeleton-data#AttachmentLoader Loading skeleton data in the Spine Runtimes Guide - */ class AtlasAttachmentLoader implements AttachmentLoader { private var atlas:TextureAtlas; @@ -50,72 +47,41 @@ class AtlasAttachmentLoader implements AttachmentLoader { this.allowMissingRegions = allowMissingRegions; } - private function loadSequence(name:String, basePath:String, sequence:Sequence) { + private function findRegions(name:String, basePath:String, sequence:Sequence):Void { var regions = sequence.regions; - for (i in 0...regions.length) { - var path = sequence.getPath(basePath, i); - regions[i] = this.atlas.findRegion(path); - if (regions[i] == null) - throw new SpineException("Region not found in atlas: " + path + " (sequence: " + name + ")"); - } + for (i in 0...regions.length) + regions[i] = findRegion(name, sequence.getPath(basePath, i)); + } + + private function findRegion(name:String, path:String):TextureAtlasRegion { + var region = atlas.findRegion(path); + if (region == null && !allowMissingRegions) + throw new SpineException("Region not found in atlas: " + path + " (attachment: " + name + ")"); + return region; } - /** - * @return May be null to not load the attachment. - */ public function newRegionAttachment(skin:Skin, name:String, path:String, sequence:Sequence):RegionAttachment { - var attachment = new RegionAttachment(name, path); - if (sequence != null) { - this.loadSequence(name, path, sequence); - } else { - var region = this.atlas.findRegion(path); - if (region == null && !this.allowMissingRegions) - throw new SpineException("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - attachment.region = region; - } - return attachment; + findRegions(name, path, sequence); + return new RegionAttachment(name, sequence); } - /** - * @return May be null to not load the attachment. In that case null should also be returned for child meshes. - */ public function newMeshAttachment(skin:Skin, name:String, path:String, sequence:Sequence):MeshAttachment { - var attachment = new MeshAttachment(name, path); - if (sequence != null) { - this.loadSequence(name, path, sequence); - } else { - var region = atlas.findRegion(path); - if (region == null && !this.allowMissingRegions) - throw new SpineException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - attachment.region = region; - } - return attachment; + findRegions(name, path, sequence); + return new MeshAttachment(name, sequence); } - /** - * @return May be null to not load the attachment. - */ public function newBoundingBoxAttachment(skin:Skin, name:String):BoundingBoxAttachment { return new BoundingBoxAttachment(name); } - /** - * @return May be null to not load the attachment. - */ public function newPathAttachment(skin:Skin, name:String):PathAttachment { return new PathAttachment(name); } - /** - * @return May be null to not load the attachment. - */ public function newPointAttachment(skin:Skin, name:String):PointAttachment { return new PointAttachment(name); } - /** - * @return May be null to not load the attachment. - */ public function newClippingAttachment(skin:Skin, name:String):ClippingAttachment { return new ClippingAttachment(name); } diff --git a/spine-haxe/spine-haxe/spine/attachments/Attachment.hx b/spine-haxe/spine-haxe/spine/attachments/Attachment.hx index 442bc7dff..7eb42e299 100644 --- a/spine-haxe/spine-haxe/spine/attachments/Attachment.hx +++ b/spine-haxe/spine-haxe/spine/attachments/Attachment.hx @@ -33,11 +33,16 @@ package spine.attachments; class Attachment { private var _name:String; + /** Timelines for the timeline attachment are also applied to this attachment. + * May be null if no attachment-specific timelines should be applied. */ + public var timelineAttachment:Attachment; + public function new(name:String) { if (name == null) { throw new SpineException("name cannot be null."); } _name = name; + timelineAttachment = this; } /** The attachment's name. */ diff --git a/spine-haxe/spine-haxe/spine/attachments/MeshAttachment.hx b/spine-haxe/spine-haxe/spine/attachments/MeshAttachment.hx index 31d0285fa..47b46696c 100644 --- a/spine-haxe/spine-haxe/spine/attachments/MeshAttachment.hx +++ b/spine-haxe/spine-haxe/spine/attachments/MeshAttachment.hx @@ -30,6 +30,9 @@ package spine.attachments; import spine.Color; +import spine.Sequence; +import spine.TextureRegion; +import spine.HasSequence; import spine.atlas.TextureAtlasRegion; import spine.atlas.TextureAtlasPage; @@ -37,60 +40,114 @@ import spine.atlas.TextureAtlasPage; * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh. * * @see https://esotericsoftware.com/spine-meshes Mesh attachments in the Spine User Guide */ -class MeshAttachment extends VertexAttachment implements HasTextureRegion { - public var region:TextureRegion; - public var path:String; +class MeshAttachment extends VertexAttachment implements HasSequence { + public var sequence:Sequence; /** The UV pair for each vertex, normalized within the texture region. */ public var regionUVs = new Array(); - /** The UV pair for each vertex, normalized within the entire texture. - * See #updateRegion(). */ - public var uvs = new Array(); - /** Triplets of vertex indices which describe the mesh's triangulation. */ public var triangles = new Array(); + /** The number of entries at the beginning of #vertices that make up the mesh hull. */ + public var hullLength:Int = 0; + + /** The name of the texture region for this attachment. */ + public var path:String; + + /** The color to tint the mesh. */ public var color:Color = new Color(1, 1, 1, 1); + /** The parent mesh if this is a linked mesh, else null. A linked mesh shares the #bones, #vertices, + * #regionUVs, #triangles, #hullLength, #edges, #width, and #height with the + * parent mesh, but may have a different #name or #path (and therefore a different texture). */ + private var _parentMesh:MeshAttachment; + + /** Vertex index pairs describing edges for controlling triangulation, or null if nonessential data was not exported. Mesh + * triangles never cross edges. Triangulation is not performed at runtime. */ + public var edges = new Array(); + /** The width of the mesh's image, or zero if nonessential data was not exported. */ public var width:Float = 0; /** The height of the mesh's image, or zero if nonessential data was not exported. */ public var height:Float = 0; - /** The number of entries at the beginning of #vertices that make up the mesh hull. */ - public var hullLength:Int = 0; - - /** Vertex index pairs describing edges for controlling triangulation, or null if nonessential data was not exported. Mesh - * triangles will never cross edges. Triangulation is not performed at runtime. */ - public var edges = new Array(); - public var rendererObject:Dynamic; - public var sequence:Sequence; - /** The parent mesh if this is a linked mesh, else null. A linked mesh shares the #bones, #vertices, - * #regionUVs, #triangles, #hullLength, #edges, #width, and #height with the - * parent mesh, but may have a different #name or #path (and therefore a different texture). */ - private var _parentMesh:MeshAttachment; - - /** Copy constructor. Use newLinkedMesh() if the other mesh is a linked mesh. */ - public function new(name:String, path:String) { + public function new(name:String, sequence:Sequence) { super(name); - this.path = path; + this.sequence = sequence; } - /** Calculates uvs using the regionUVs and region. Must be called if the region, the region's properties, or - * the regionUVs are changed. */ - public function updateRegion():Void { - if (region == null) { - throw new SpineException("Region not set."); - return; + override public function copy():Attachment { + if (_parentMesh != null) + return newLinkedMesh(); + + var copy = new MeshAttachment(name, sequence.copy()); + copy.path = path; + copy.color.setFromColor(color); + copy.rendererObject = rendererObject; + + this.copyTo(copy); + copy.regionUVs = regionUVs.copy(); + copy.triangles = triangles.copy(); + copy.hullLength = hullLength; + + if (edges != null) { + copy.edges = edges.copy(); } - var regionUVs = this.regionUVs; - if (uvs.length != regionUVs.length) { - uvs = new Array(); - uvs.resize(regionUVs.length); + copy.width = width; + copy.height = height; + + return copy; + } + + /** Calls Sequence.update() on this attachment's sequence. */ + public function updateSequence():Void { + sequence.update(this); + } + + public var parentMesh(get, set):MeshAttachment; + + private function get_parentMesh():MeshAttachment { + return _parentMesh; + } + + private function set_parentMesh(parentMesh:MeshAttachment):MeshAttachment { + _parentMesh = parentMesh; + if (parentMesh != null) { + bones = parentMesh.bones; + vertices = parentMesh.vertices; + worldVerticesLength = parentMesh.worldVerticesLength; + regionUVs = parentMesh.regionUVs; + triangles = parentMesh.triangles; + hullLength = parentMesh.hullLength; + edges = parentMesh.edges; + width = parentMesh.width; + height = parentMesh.height; + } + return _parentMesh; + } + + /** Returns a new mesh with the parentMesh set to this mesh's parent mesh, if any, else to this mesh. */ + public function newLinkedMesh():MeshAttachment { + var copy = new MeshAttachment(name, sequence.copy()); + copy.rendererObject = rendererObject; + copy.timelineAttachment = timelineAttachment; + copy.path = path; + copy.color.setFromColor(color); + copy.parentMesh = _parentMesh != null ? _parentMesh : this; + copy.updateSequence(); + return copy; + } + + /** Computes UVs for a mesh attachment. + * @param uvs Output array for the computed UVs, same length as regionUVs. */ + public static function computeUVs(region:TextureRegion, regionUVs:Array, uvs:Array):Void { + if (region == null) { + throw "Region not set."; + return; } var n = uvs.length; var u = region.u, v = region.v, width:Float = 0, height:Float = 0; @@ -145,8 +202,8 @@ class MeshAttachment extends VertexAttachment implements HasTextureRegion { u = v = 0; width = height = 1; } else { - width = this.region.u2 - u; - height = this.region.v2 - v; + width = region.u2 - u; + height = region.v2 - v; } var i = 0; while (i < n) { @@ -155,72 +212,4 @@ class MeshAttachment extends VertexAttachment implements HasTextureRegion { i += 2; } } - - public var parentMesh(get, set):MeshAttachment; - - private function get_parentMesh():MeshAttachment { - return _parentMesh; - } - - private function set_parentMesh(parentMesh:MeshAttachment):MeshAttachment { - _parentMesh = parentMesh; - if (parentMesh != null) { - bones = parentMesh.bones; - vertices = parentMesh.vertices; - worldVerticesLength = parentMesh.worldVerticesLength; - regionUVs = parentMesh.regionUVs; - triangles = parentMesh.triangles; - hullLength = parentMesh.hullLength; - edges = parentMesh.edges; - width = parentMesh.width; - height = parentMesh.height; - } - return _parentMesh; - } - - override public function copy():Attachment { - if (parentMesh != null) - return newLinkedMesh(); - - var copy:MeshAttachment = new MeshAttachment(name, this.path); - copy.region = region; - copy.color.setFromColor(color); - copy.rendererObject = rendererObject; - - this.copyTo(copy); - copy.regionUVs = regionUVs.copy(); - copy.uvs = uvs.copy(); - copy.triangles = triangles.copy(); - copy.hullLength = hullLength; - - copy.sequence = sequence != null ? sequence.copy() : null; - - if (edges != null) { - copy.edges = edges.copy(); - } - copy.width = width; - copy.height = height; - - return copy; - } - - /** If the attachment has a sequence, the region may be changed. */ - public override function computeWorldVertices(skeleton:Skeleton, slot:Slot, start:Int, count:Int, worldVertices:Array, offset:Int, stride:Int):Void { - if (sequence != null) - sequence.apply(slot.applied, this); - super.computeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride); - } - - /** Returns a new mesh with the parentMesh set to this mesh's parent mesh, if any, else to this mesh. */ - public function newLinkedMesh():MeshAttachment { - var copy:MeshAttachment = new MeshAttachment(name, path); - copy.rendererObject = rendererObject; - copy.region = region; - copy.color.setFromColor(color); - copy.timelineAttachment = timelineAttachment; - copy.parentMesh = this.parentMesh != null ? this.parentMesh : this; - if (copy.region != null) - copy.updateRegion(); - return copy; - } } diff --git a/spine-haxe/spine-haxe/spine/attachments/RegionAttachment.hx b/spine-haxe/spine-haxe/spine/attachments/RegionAttachment.hx index 332647118..7ed15393f 100644 --- a/spine-haxe/spine-haxe/spine/attachments/RegionAttachment.hx +++ b/spine-haxe/spine-haxe/spine/attachments/RegionAttachment.hx @@ -30,12 +30,18 @@ package spine.attachments; import spine.Color; +import spine.Sequence; +import spine.SlotPose; +import spine.Slot; +import spine.TextureRegion; +import spine.MathUtils; +import spine.HasSequence; /** An attachment that displays a textured quadrilateral. * * @see https://esotericsoftware.com/spine-regions Region attachments in the Spine User Guide */ -class RegionAttachment extends Attachment implements HasTextureRegion { +class RegionAttachment extends Attachment implements HasSequence { public static inline var BLX:Int = 0; public static inline var BLY:Int = 1; public static inline var ULX:Int = 2; @@ -45,6 +51,8 @@ class RegionAttachment extends Attachment implements HasTextureRegion { public static inline var BRX:Int = 6; public static inline var BRY:Int = 7; + public var sequence:Sequence; + /** The local x translation. */ public var x:Float = 0; @@ -66,31 +74,86 @@ class RegionAttachment extends Attachment implements HasTextureRegion { /** The height of the region attachment in Spine. */ public var height:Float = 0; - public var color:Color = new Color(1, 1, 1, 1); + /** The name of the texture region for this attachment. */ public var path:String; + + /** The color to tint the region attachment. */ + public var color:Color = new Color(1, 1, 1, 1); + public var rendererObject:Dynamic; - public var region:TextureRegion; - public var sequence:Sequence; - /** For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. - * - * See RegionAttachment.updateRegion(). */ - private var offset:Array = new Array(); - - public var uvs:Array = new Array(); - - /** - * @param name The attachment name. - * @param path The path used to find the region for the attachment. - */ - public function new(name:String, path:String) { + public function new(name:String, sequence:Sequence) { super(name); - this.path = path; + this.sequence = sequence; } - /** Calculates the RegionAttachment.offsets and RegionAttachment.uvs using the region and the attachment's transform. Must be called if the - * region, the region's properties, or the transform are changed. */ - public function updateRegion():Void { + override public function copy():Attachment { + var copy = new RegionAttachment(name, sequence.copy()); + 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.color.setFromColor(color); + return copy; + } + + /** Transforms the attachment's four vertices to world coordinates. + * + * @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide + * @param worldVertices The output world vertices. Must have a length >= offset + 8. + * @param vertexOffsets The vertex offsets from the sequence. + * @param offset The worldVertices index to begin writing values. + * @param stride The number of worldVertices entries between the value pairs written. */ + public function computeWorldVertices(slot:Slot, vertexOffsets:Array, worldVertices:Array, offset:Int, stride:Int):Void { + var bone = slot.bone.applied; + var x = bone.worldX, y = bone.worldY; + var a = bone.a, b = bone.b, c = bone.c, d = bone.d; + + var offsetX = vertexOffsets[0]; + var offsetY = vertexOffsets[1]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = vertexOffsets[2]; + offsetY = vertexOffsets[3]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = vertexOffsets[4]; + offsetY = vertexOffsets[5]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = vertexOffsets[6]; + offsetY = vertexOffsets[7]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + } + + /** Returns the vertex offsets for the given slot pose. */ + public function getOffsets(pose:SlotPose):Array { + return sequence.offsets[sequence.resolveIndex(pose)]; + } + + /** Calls Sequence.update() on this attachment's sequence. */ + public function updateSequence():Void { + sequence.update(this); + } + + /** Computes UVs and offsets for a region attachment. + * @param uvs Output array for the computed UVs, length of 8. + * @param offset Output array for the computed vertex offsets, length of 8. */ + public static function computeUVs(region:TextureRegion, x:Float, y:Float, scaleX:Float, scaleY:Float, rotation:Float, + width:Float, height:Float, offset:Array, uvs:Array):Void { + + if (region == null) throw "Region not set."; var regionScaleX = width / region.originalWidth * scaleX; var regionScaleY = height / region.originalHeight * scaleY; var localX = -width / 2 * scaleX + region.offsetX * regionScaleX; @@ -100,7 +163,6 @@ class RegionAttachment extends Attachment implements HasTextureRegion { var radians = rotation * MathUtils.degRad; var cos = Math.cos(radians); var sin = Math.sin(radians); - var x = this.x, y = this.y; var localXCos = localX * cos + x; var localXSin = localX * sin; var localYCos = localY * cos + y; @@ -109,7 +171,6 @@ class RegionAttachment extends Attachment implements HasTextureRegion { var localX2Sin = localX2 * sin; var localY2Cos = localY2 * cos + y; var localY2Sin = localY2 * sin; - offset[0] = localXCos - localYSin; offset[1] = localYCos + localXSin; offset[2] = localXCos - localY2Sin; @@ -128,83 +189,22 @@ class RegionAttachment extends Attachment implements HasTextureRegion { uvs[5] = 1; uvs[6] = 1; uvs[7] = 0; - } else if (region.degrees == 90) { - uvs[0] = region.u2; - uvs[1] = region.v2; - uvs[2] = region.u; - uvs[3] = region.v2; - uvs[4] = region.u; - uvs[5] = region.v; - uvs[6] = region.u2; - uvs[7] = region.v; } else { - uvs[0] = region.u; uvs[1] = region.v2; uvs[2] = region.u; - uvs[3] = region.v; - uvs[4] = region.u2; uvs[5] = region.v; uvs[6] = region.u2; - uvs[7] = region.v2; + if (region.degrees == 90) { + uvs[0] = region.u2; + uvs[3] = region.v2; + uvs[4] = region.u; + uvs[7] = region.v; + } else { + uvs[0] = region.u; + uvs[3] = region.v; + uvs[4] = region.u2; + uvs[7] = region.v2; + } } } - - /** Transforms the attachment's four vertices to world coordinates. If the attachment has a RegionAttachment.sequence, the region may - * be changed. - * - * @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide - * @param worldVertices The output world vertices. Must have a length >= offset + 8. - * @param offset The worldVertices index to begin writing values. - * @param stride The number of worldVertices entries between the value pairs written. */ - public function computeWorldVertices(slot:Slot, worldVertices:Array, offset:Int, stride:Int):Void { - if (sequence != null) - sequence.apply(slot.applied, this); - - var vertexOffset = this.offset; - var bone = slot.bone.applied; - var x = bone.worldX, y = bone.worldY; - var a = bone.a, b = bone.b, c = bone.c, d = bone.d; - var offsetX:Float = 0, offsetY:Float = 0; - - offsetX = vertexOffset[0]; - offsetY = vertexOffset[1]; - worldVertices[offset] = offsetX * a + offsetY * b + x; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + y; - offset += stride; - - offsetX = vertexOffset[2]; - offsetY = vertexOffset[3]; - worldVertices[offset] = offsetX * a + offsetY * b + x; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + y; - offset += stride; - - offsetX = vertexOffset[4]; - offsetY = vertexOffset[5]; - worldVertices[offset] = offsetX * a + offsetY * b + x; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + y; - offset += stride; - - offsetX = vertexOffset[6]; - offsetY = vertexOffset[7]; - worldVertices[offset] = offsetX * a + offsetY * b + x; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + y; - } - - override public function copy():Attachment { - var copy:RegionAttachment = new RegionAttachment(name, path); - copy.region = region; - copy.rendererObject = rendererObject; - copy.x = x; - copy.y = y; - copy.scaleX = scaleX; - copy.scaleY = scaleY; - copy.rotation = rotation; - copy.width = width; - copy.height = height; - copy.uvs = uvs.copy(); - copy.offset = offset.copy(); - copy.color.setFromColor(color); - copy.sequence = sequence != null ? sequence.copy() : null; - return copy; - } } diff --git a/spine-haxe/spine-haxe/spine/attachments/VertexAttachment.hx b/spine-haxe/spine-haxe/spine/attachments/VertexAttachment.hx index ea2dfaf66..ac6c4f105 100644 --- a/spine-haxe/spine-haxe/spine/attachments/VertexAttachment.hx +++ b/spine-haxe/spine-haxe/spine/attachments/VertexAttachment.hx @@ -55,13 +55,8 @@ class VertexAttachment extends Attachment { /** Returns a unique ID for this attachment. */ public var id:Int = nextID++; - /** Timelines for the timeline attachment are also applied to this attachment. - * May be null if no attachment-specific timelines should be applied. */ - public var timelineAttachment:VertexAttachment; - public function new(name:String) { super(name); - timelineAttachment = this; } /** Transforms the attachment's local vertices to world coordinates. If the slot's spine.SlotPose.deform is diff --git a/spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx b/spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx index b248423cb..8ed9f3899 100644 --- a/spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx +++ b/spine-haxe/spine-haxe/spine/flixel/SkeletonSprite.hx @@ -174,12 +174,14 @@ class SkeletonSprite extends FlxTypedGroup { numFloats = clippedVertexSize << 2; if (numFloats > worldVertices.length) worldVertices.resize(numFloats); - region.computeWorldVertices(slot, worldVertices, 0, clippedVertexSize); + var sequence = region.sequence; + var sequenceIndex = sequence.resolveIndex(pose); + region.computeWorldVertices(slot, sequence.offsets[sequenceIndex], worldVertices, 0, clippedVertexSize); mesh = getFlixelMeshFromRendererAttachment(region); - mesh.graphic = region.region.texture; + mesh.graphic = sequence.regions[sequenceIndex].texture; triangles = QUAD_INDICES; - uvs = region.uvs; + uvs = sequence.getUVs(sequenceIndex); attachmentColor = region.color; } else if (Std.isOfType(attachment, MeshAttachment)) { var meshAttachment:MeshAttachment = cast(attachment, MeshAttachment); @@ -190,10 +192,12 @@ class SkeletonSprite extends FlxTypedGroup { } meshAttachment.computeWorldVertices(skeleton, slot, 0, meshAttachment.worldVerticesLength, worldVertices, 0, clippedVertexSize); + var sequence = meshAttachment.sequence; + var sequenceIndex = sequence.resolveIndex(pose); mesh = getFlixelMeshFromRendererAttachment(meshAttachment); - mesh.graphic = meshAttachment.region.texture; + mesh.graphic = sequence.regions[sequenceIndex].texture; triangles = meshAttachment.triangles; - uvs = meshAttachment.uvs; + uvs = sequence.getUVs(sequenceIndex); attachmentColor = meshAttachment.color; } else if (Std.isOfType(attachment, ClippingAttachment)) { var clip:ClippingAttachment = cast(attachment, ClippingAttachment); @@ -553,5 +557,4 @@ class SkeletonSprite extends FlxTypedGroup { typedef RenderedAttachment = { var rendererObject:Dynamic; - var region:TextureRegion; } diff --git a/spine-haxe/spine-haxe/spine/starling/SkeletonSprite.hx b/spine-haxe/spine-haxe/spine/starling/SkeletonSprite.hx index 41cb2ea73..9636056f0 100644 --- a/spine-haxe/spine-haxe/spine/starling/SkeletonSprite.hx +++ b/spine-haxe/spine-haxe/spine/starling/SkeletonSprite.hx @@ -127,15 +127,18 @@ class SkeletonSprite extends DisplayObject implements IAnimatable { verticesCount = verticesLength >> 1; if (worldVertices.length < verticesLength) worldVertices.resize(verticesLength); - region.computeWorldVertices(slot, worldVertices, 0, 2); + var sequence = region.sequence; + var sequenceIndex = sequence.resolveIndex(pose); + region.computeWorldVertices(slot, sequence.offsets[sequenceIndex], worldVertices, 0, 2); mesh = null; + var regionTexture = sequence.regions[sequenceIndex].texture; if (Std.isOfType(region.rendererObject, SkeletonMesh)) { mesh = cast(region.rendererObject, SkeletonMesh); - mesh.texture = region.region.texture; + mesh.texture = regionTexture; indices = QUAD_INDICES; } else { - mesh = region.rendererObject = new SkeletonMesh(cast(region.region.texture, Texture)); + mesh = region.rendererObject = new SkeletonMesh(cast(regionTexture, Texture)); indexData = mesh.getIndexData(); indices = QUAD_INDICES; @@ -148,7 +151,7 @@ class SkeletonSprite extends DisplayObject implements IAnimatable { indexData = mesh.getIndexData(); attachmentColor = region.color; - uvs = region.uvs; + uvs = sequence.getUVs(sequenceIndex); } else if (Std.isOfType(attachment, MeshAttachment)) { var meshAttachment:MeshAttachment = cast(attachment, MeshAttachment); verticesLength = meshAttachment.worldVerticesLength; @@ -157,13 +160,16 @@ class SkeletonSprite extends DisplayObject implements IAnimatable { worldVertices.resize(verticesLength); meshAttachment.computeWorldVertices(skeleton, slot, 0, meshAttachment.worldVerticesLength, worldVertices, 0, 2); + var sequence = meshAttachment.sequence; + var sequenceIndex = sequence.resolveIndex(pose); mesh = null; + var regionTexture = sequence.regions[sequenceIndex].texture; if (Std.isOfType(meshAttachment.rendererObject, SkeletonMesh)) { mesh = cast(meshAttachment.rendererObject, SkeletonMesh); - mesh.texture = meshAttachment.region.texture; + mesh.texture = regionTexture; indices = meshAttachment.triangles; } else { - mesh = meshAttachment.rendererObject = new SkeletonMesh(cast(meshAttachment.region.texture, Texture)); + mesh = meshAttachment.rendererObject = new SkeletonMesh(cast(regionTexture, Texture)); indexData = mesh.getIndexData(); indices = meshAttachment.triangles; @@ -177,7 +183,7 @@ class SkeletonSprite extends DisplayObject implements IAnimatable { indexData = mesh.getIndexData(); attachmentColor = meshAttachment.color; - uvs = meshAttachment.uvs; + uvs = sequence.getUVs(sequenceIndex); } else if (Std.isOfType(attachment, ClippingAttachment)) { var clip:ClippingAttachment = cast(attachment, ClippingAttachment); clipper.clipEnd(slot);