mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-25 22:23:42 +08:00
[haxe] Port latest libgdx timeline, sequence, draw order, and follow-up fixes. See #2989.
This commit is contained in:
parent
f7ed100aa8
commit
2ba91231f3
@ -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;
|
||||
}
|
||||
@ -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<TextureRegion>;
|
||||
public var pathSuffix:Bool;
|
||||
public var uvs:Array<Array<Float>>;
|
||||
public var offsets:Array<Array<Float>>;
|
||||
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<TextureRegion>();
|
||||
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<Array<Float>>();
|
||||
copy.uvs.resize(regionCount);
|
||||
for (i in 0...regionCount) {
|
||||
copy.uvs[i] = new Array<Float>();
|
||||
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<Array<Float>>();
|
||||
copy.offsets.resize(regionCount);
|
||||
for (i in 0...regionCount) {
|
||||
copy.offsets[i] = new Array<Float>();
|
||||
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<Array<Float>>();
|
||||
this.uvs.resize(regionCount);
|
||||
this.offsets = new Array<Array<Float>>();
|
||||
this.offsets.resize(regionCount);
|
||||
for (i in 0...regionCount) {
|
||||
this.uvs[i] = new Array<Float>();
|
||||
this.uvs[i].resize(8);
|
||||
this.offsets[i] = new Array<Float>();
|
||||
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<Array<Float>>();
|
||||
this.uvs.resize(regionCount);
|
||||
this.offsets = null;
|
||||
for (i in 0...regionCount) {
|
||||
this.uvs[i] = new Array<Float>();
|
||||
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<Float> {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<Float> = 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<Int> = new Array<Int>();
|
||||
var weights:Array<Float> = new Array<Float>();
|
||||
var bonesArray:Array<Int> = new Array<Int>();
|
||||
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<Int> {
|
||||
var changeCount:Int = input.readInt(true);
|
||||
if (changeCount == 0)
|
||||
return null;
|
||||
var drawOrder:Array<Int> = new Array<Int>();
|
||||
drawOrder.resize(slotCount);
|
||||
for (i in 0...slotCount)
|
||||
drawOrder[i] = -1;
|
||||
var unchanged:Array<Int> = new Array<Int>();
|
||||
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<Float> {
|
||||
var array:Array<Float> = new Array<Float>();
|
||||
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<Int> = new Array<Int>();
|
||||
drawOrder.resize(slotCount);
|
||||
var ii:Int = slotCount - 1;
|
||||
while (ii >= 0) {
|
||||
drawOrder[ii--] = -1;
|
||||
}
|
||||
var unchanged:Array<Int> = new Array<Int>();
|
||||
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<Int> = new Array<Int>();
|
||||
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) {
|
||||
|
||||
@ -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<Animation>):Array<Animation> {
|
||||
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.
|
||||
|
||||
@ -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<Int>):Array<Int> {
|
||||
var changes:Array<Dynamic> = Reflect.getProperty(keyMap, "offsets");
|
||||
if (changes == null) return null;
|
||||
var drawOrder:Array<Int> = new Array<Int>();
|
||||
drawOrder.resize(slotCount);
|
||||
for (i in 0...slotCount)
|
||||
drawOrder[i] = -1;
|
||||
var unchanged:Array<Int> = new Array<Int>();
|
||||
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<Float> = 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<Dynamic> = cast(Reflect.field(map, "drawOrder"), Array<Dynamic>);
|
||||
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<Int> = null;
|
||||
var offsets:Array<Dynamic> = Reflect.getProperty(drawOrderMap, "offsets");
|
||||
if (offsets != null) {
|
||||
drawOrder = new Array<Int>();
|
||||
drawOrder.resize(slotCount);
|
||||
var i = slotCount - 1;
|
||||
while (i >= 0) {
|
||||
drawOrder[i--] = -1;
|
||||
}
|
||||
var unchanged:Array<Int> = new Array<Int>();
|
||||
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<Dynamic> = cast(Reflect.field(map, "drawOrderFolder"), Array<Dynamic>);
|
||||
if (drawOrderFolders != null) {
|
||||
for (timelineMap in drawOrderFolders) {
|
||||
var slotEntries:Array<Dynamic> = cast(Reflect.field(timelineMap, "slots"), Array<Dynamic>);
|
||||
var folderSlots:Array<Int> = new Array<Int>();
|
||||
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<Dynamic> = cast(Reflect.field(timelineMap, "keys"), Array<Dynamic>);
|
||||
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<Dynamic> = cast(Reflect.field(map, "events"), Array<Dynamic>);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
126
spine-haxe/spine-haxe/spine/animation/DrawOrderFolderTimeline.hx
Normal file
126
spine-haxe/spine-haxe/spine/animation/DrawOrderFolderTimeline.hx
Normal file
@ -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<Int>;
|
||||
private var inFolder:Array<Bool>;
|
||||
private var drawOrders:Array<Array<Int>>;
|
||||
|
||||
/** @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<Int>, slotCount:Int) {
|
||||
super(frameCount, Property.drawOrder);
|
||||
this.slots = slots;
|
||||
drawOrders = new Array<Array<Int>>();
|
||||
drawOrders.resize(frameCount);
|
||||
inFolder = new Array<Bool>();
|
||||
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<Int> {
|
||||
return slots;
|
||||
}
|
||||
|
||||
/** The draw order for each frame. See setFrame(). */
|
||||
public function getDrawOrders():Array<Array<Int>> {
|
||||
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<Int>):Void {
|
||||
frames[frame] = time;
|
||||
drawOrders[frame] = drawOrder;
|
||||
}
|
||||
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, 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<Int>):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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Int>):Void {
|
||||
frames[frame] = time;
|
||||
drawOrders[frame] = drawOrder;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<Event>, 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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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. */
|
||||
|
||||
@ -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<Float>();
|
||||
|
||||
/** The UV pair for each vertex, normalized within the entire texture.
|
||||
* See #updateRegion(). */
|
||||
public var uvs = new Array<Float>();
|
||||
|
||||
/** Triplets of vertex indices which describe the mesh's triangulation. */
|
||||
public var triangles = new Array<Int>();
|
||||
|
||||
/** 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<Int>();
|
||||
|
||||
/** 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<Int>();
|
||||
|
||||
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<Float>();
|
||||
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<Float>, uvs:Array<Float>):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<Float>, 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Float> = new Array<Float>();
|
||||
|
||||
public var uvs:Array<Float> = new Array<Float>();
|
||||
|
||||
/**
|
||||
* @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<Float>, worldVertices:Array<Float>, 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<Float> {
|
||||
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<Float>, uvs:Array<Float>):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<Float>, 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -174,12 +174,14 @@ class SkeletonSprite extends FlxTypedGroup<FlxObject> {
|
||||
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<FlxObject> {
|
||||
}
|
||||
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<FlxObject> {
|
||||
|
||||
typedef RenderedAttachment = {
|
||||
var rendererObject:Dynamic;
|
||||
var region:TextureRegion;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user