diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 5ae81fb19..ae39712b4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -164,7 +164,8 @@ public class Animation { attachment, color, deform, // event, drawOrder, // ikConstraint, transformConstraint, // - pathConstraintPosition, pathConstraintSpacing, pathConstraintMix + pathConstraintPosition, pathConstraintSpacing, pathConstraintMix, // + twoColor } /** The base class for timelines that use interpolation between key frame values. */ @@ -621,6 +622,119 @@ public class Animation { } } + /** Changes a slot's {@link Slot#getColor()} and {@link Slot#getDarkColor()} for two color tinting. */ + static public class TwoColorTimeline extends CurveTimeline { + static public final int ENTRIES = 9; + static private final int PREV_TIME = -9, PREV_R = -8, PREV_G = -7, PREV_B = -6, PREV_A = -5; + static private final int PREV_R2 = -4, PREV_G2 = -3, PREV_B2 = -2, PREV_A2 = -1; + static private final int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7, A2 = 8; + + int slotIndex; + private final float[] frames; // time, r, g, b, a, r2, g2, b2, a2, ... + + public TwoColorTimeline (int frameCount) { + super(frameCount); + frames = new float[frameCount * ENTRIES]; + } + + public int getPropertyId () { + return (TimelineType.color.ordinal() << 24) + slotIndex; + } + + public void setSlotIndex (int index) { + if (index < 0) throw new IllegalArgumentException("index must be >= 0."); + this.slotIndex = index; + } + + /** The index of the slot in {@link Skeleton#getSlots()} that will be changed. */ + public int getSlotIndex () { + return slotIndex; + } + + /** The time in seconds, red, green, blue, and alpha values for each key frame. */ + public float[] getFrames () { + return frames; + } + + /** Sets the time in seconds, light, and dark colors for the specified key frame. */ + public void setFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2, + float a2) { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + frames[frameIndex + R2] = r2; + frames[frameIndex + G2] = g2; + frames[frameIndex + B2] = b2; + frames[frameIndex + A2] = a2; + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, + boolean mixingOut) { + + Slot slot = skeleton.slots.get(slotIndex); + float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. + if (setupPose) { + slot.color.set(slot.data.color); + slot.darkColor.set(slot.data.darkColor); + } + return; + } + + float r, g, b, a, r2, g2, b2, a2; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + int i = frames.length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + r2 = frames[i + PREV_R2]; + g2 = frames[i + PREV_G2]; + b2 = frames[i + PREV_B2]; + a2 = frames[i + PREV_A2]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = binarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + r2 = frames[frame + PREV_R2]; + g2 = frames[frame + PREV_G2]; + b2 = frames[frame + PREV_B2]; + a2 = frames[frame + PREV_A2]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + r2 += (frames[frame + R2] - r2) * percent; + g2 += (frames[frame + G2] - g2) * percent; + b2 += (frames[frame + B2] - b2) * percent; + a2 += (frames[frame + A2] - a2) * percent; + } + if (alpha == 1) { + slot.color.set(r, g, b, a); + slot.darkColor.set(r2, g2, b2, a2); + } else { + Color light = slot.color; + Color dark = slot.darkColor; + if (setupPose) { + light.set(slot.data.color); + dark.set(slot.data.darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + dark.add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, (a2 - dark.a) * alpha); + } + } + } + /** Changes a slot's {@link Slot#getAttachment()}. */ static public class AttachmentTimeline implements Timeline { int slotIndex; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java index 5bef94a78..51985cf1b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -42,7 +42,7 @@ import com.esotericsoftware.spine.attachments.VertexAttachment; public class Slot { final SlotData data; final Bone bone; - final Color color; + final Color color = new Color(), darkColor; Attachment attachment; private float attachmentTime; private FloatArray attachmentVertices = new FloatArray(); @@ -52,7 +52,7 @@ public class Slot { if (bone == null) throw new IllegalArgumentException("bone cannot be null."); this.data = data; this.bone = bone; - color = new Color(); + darkColor = data.darkColor == null ? null : new Color(); setToSetupPose(); } @@ -62,7 +62,8 @@ public class Slot { if (bone == null) throw new IllegalArgumentException("bone cannot be null."); data = slot.data; this.bone = bone; - color = new Color(slot.color); + color.set(slot.color); + darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); attachment = slot.attachment; attachmentTime = slot.attachmentTime; } @@ -82,11 +83,17 @@ public class Slot { return bone.skeleton; } - /** The color used to tint the slot's attachment. */ + /** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two + * color tinting. */ public Color getColor () { return color; } + /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. */ + public Color getDarkColor () { + return darkColor; + } + /** The current attachment for the slot, or null if the slot has no attachment. */ public Attachment getAttachment () { return attachment; @@ -128,6 +135,7 @@ public class Slot { /** Sets this slot to the setup pose. */ public void setToSetupPose () { color.set(data.color); + if (darkColor != null) darkColor.set(data.darkColor); if (data.attachmentName == null) setAttachment(null); else { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java index 22184f919..7f1417fec 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java @@ -38,6 +38,7 @@ public class SlotData { final String name; final BoneData boneData; final Color color = new Color(1, 1, 1, 1); + Color darkColor; String attachmentName; BlendMode blendMode; @@ -65,11 +66,21 @@ public class SlotData { return boneData; } - /** The color used to tint the slot's attachment. */ + /** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two + * color tinting. */ public Color getColor () { return color; } + /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. */ + public Color getDarkColor () { + return darkColor; + } + + public void setDarkColor (Color darkColor) { + this.darkColor = darkColor; + } + /** @param attachmentName May be null. */ public void setAttachmentName (String attachmentName) { this.attachmentName = attachmentName;