/** * Adds an empty animation to be played after the current or last queued animation for a track, * and sets the track entry's {@link TrackEntry#getMixDuration()}. If the track is empty, it is * equivalent to calling {@link #setEmptyAnimation(int, float)}. * * @param delay Seconds to begin this animation after the start of the previous animation. May be * <= 0 to use the animation duration of the previous track minus any mix duration plus <code> * delay</code>. * @return A track entry to allow further customization of animation playback. References to the * track entry must not be kept after the {@link AnimationStateListener#dispose(TrackEntry)} * event occurs. */ public TrackEntry addEmptyAnimation(int trackIndex, float mixDuration, float delay) { if (delay <= 0) delay -= mixDuration; TrackEntry entry = addAnimation(trackIndex, emptyAnimation, false, delay); entry.mixDuration = mixDuration; entry.trackEnd = mixDuration; return entry; }
/** * Adds an animation to be played after the current or last queued animation for a track. If the * track is empty, it is equivalent to calling {@link #setAnimation(int, Animation, boolean)}. * * @param delay Seconds to begin this animation after the start of the previous animation. May be * <= 0 to use the animation duration of the previous track minus any mix duration plus the * <code>delay</code>. * @return A track entry to allow further customization of animation playback. References to the * track entry must not be kept after the {@link AnimationStateListener#dispose(TrackEntry)} * event occurs. */ public TrackEntry addAnimation(int trackIndex, Animation animation, boolean loop, float delay) { if (animation == null) throw new IllegalArgumentException("animation cannot be null."); TrackEntry last = expandToIndex(trackIndex); if (last != null) { while (last.next != null) last = last.next; } TrackEntry entry = trackEntry(trackIndex, animation, loop, last); if (last == null) { setCurrent(trackIndex, entry, true); queue.drain(); } else { last.next = entry; if (delay <= 0) { float duration = last.animationEnd - last.animationStart; if (duration != 0) delay += duration * (1 + (int) (last.trackTime / duration)) - data.getMix(last.animation, animation); else delay = 0; } } entry.delay = delay; return entry; }
/** * Poses the skeleton using the track entry animations. There are no side effects other than * invoking listeners, so the animation state can be applied to multiple skeletons to pose them * identically. */ public void apply(Skeleton skeleton) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (animationsChanged) animationsChanged(); Array<Event> events = this.events; for (int i = 0, n = tracks.size; i < n; i++) { TrackEntry current = tracks.get(i); if (current == null || current.delay > 0) continue; // Apply mixing from entries first. float mix = current.alpha; if (current.mixingFrom != null) mix *= applyMixingFrom(current, skeleton); else if (current.trackTime >= current.trackEnd) // mix = 0; // Set to setup pose the last time the entry will be applied. // Apply current entry. float animationLast = current.animationLast, animationTime = current.getAnimationTime(); int timelineCount = current.animation.timelines.size; Object[] timelines = current.animation.timelines.items; if (mix == 1) { for (int ii = 0; ii < timelineCount; ii++) ((Timeline) timelines[ii]) .apply(skeleton, animationLast, animationTime, events, 1, true, false); } else { boolean firstFrame = current.timelinesRotation.size == 0; if (firstFrame) current.timelinesRotation.setSize(timelineCount << 1); float[] timelinesRotation = current.timelinesRotation.items; boolean[] timelinesFirst = current.timelinesFirst.items; for (int ii = 0; ii < timelineCount; ii++) { Timeline timeline = (Timeline) timelines[ii]; if (timeline instanceof RotateTimeline) { applyRotateTimeline( timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1, firstFrame); } else timeline.apply( skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false); } } queueEvents(current, animationTime); events.clear(); current.nextAnimationLast = animationTime; current.nextTrackLast = current.trackTime; } queue.drain(); }
public String toString() { StringBuilder buffer = new StringBuilder(64); for (int i = 0, n = tracks.size; i < n; i++) { TrackEntry entry = tracks.get(i); if (entry == null) continue; if (buffer.length() > 0) buffer.append(", "); buffer.append(entry.toString()); } if (buffer.length() == 0) return "<none>"; return buffer.toString(); }
private float applyMixingFrom(TrackEntry entry, Skeleton skeleton) { TrackEntry from = entry.mixingFrom; if (from.mixingFrom != null) applyMixingFrom(from, skeleton); float mix; if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes. mix = 1; else { mix = entry.mixTime / entry.mixDuration; if (mix > 1) mix = 1; } Array<Event> events = mix < from.eventThreshold ? this.events : null; boolean attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; float animationLast = from.animationLast, animationTime = from.getAnimationTime(); int timelineCount = from.animation.timelines.size; Object[] timelines = from.animation.timelines.items; boolean[] timelinesFirst = from.timelinesFirst.items; float alpha = from.alpha * entry.mixAlpha * (1 - mix); boolean firstFrame = from.timelinesRotation.size == 0; if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); float[] timelinesRotation = from.timelinesRotation.items; for (int i = 0; i < timelineCount; i++) { Timeline timeline = (Timeline) timelines[i]; boolean setupPose = timelinesFirst[i]; if (timeline instanceof RotateTimeline) applyRotateTimeline( timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame); else { if (!setupPose) { if (!attachments && timeline instanceof AttachmentTimeline) continue; if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; } timeline.apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true); } } if (entry.mixDuration > 0) queueEvents(from, animationTime); this.events.clear(); from.nextAnimationLast = animationTime; from.nextTrackLast = from.trackTime; return mix; }
private void disposeNext(TrackEntry entry) { TrackEntry next = entry.next; while (next != null) { queue.dispose(next); next = next.next; } entry.next = null; }
private void setCurrent(int index, TrackEntry current, boolean interrupt) { TrackEntry from = expandToIndex(index); tracks.set(index, current); if (from != null) { if (interrupt) queue.interrupt(from); current.mixingFrom = from; current.mixTime = 0; from.timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in. // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. if (from.mixingFrom != null && from.mixDuration > 0) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); } queue.start(current); }
/** * Removes all animations from the track, leaving skeletons in their previous pose. * * <p>It may be desired to use {@link AnimationState#setEmptyAnimation(int, float)} to mix the * skeletons back to the setup pose, rather than leaving them in their previous pose. */ public void clearTrack(int trackIndex) { if (trackIndex >= tracks.size) return; TrackEntry current = tracks.get(trackIndex); if (current == null) return; queue.end(current); disposeNext(current); TrackEntry entry = current; while (true) { TrackEntry from = entry.mixingFrom; if (from == null) break; queue.end(from); entry.mixingFrom = null; entry = from; } tracks.set(current.trackIndex, null); queue.drain(); }
private void updateMixingFrom(TrackEntry entry, float delta) { TrackEntry from = entry.mixingFrom; if (from == null) return; updateMixingFrom(from, delta); if (entry.mixTime >= entry.mixDuration && from.mixingFrom == null && entry.mixTime > 0) { entry.mixingFrom = null; queue.end(from); return; } from.animationLast = from.nextAnimationLast; from.trackLast = from.nextTrackLast; from.trackTime += delta * from.timeScale; entry.mixTime += delta * entry.timeScale; }
/** * Increments each track entry {@link TrackEntry#getTrackTime()}, setting queued animations as * current if needed. */ public void update(float delta) { delta *= timeScale; for (int i = 0, n = tracks.size; i < n; i++) { TrackEntry current = tracks.get(i); if (current == null) continue; current.animationLast = current.nextAnimationLast; current.trackLast = current.nextTrackLast; float currentDelta = delta * current.timeScale; if (current.delay > 0) { current.delay -= currentDelta; if (current.delay > 0) continue; currentDelta = -current.delay; current.delay = 0; } TrackEntry next = current.next; if (next != null) { // When the next entry's delay is passed, change to the next entry, preserving leftover // time. float nextTime = current.trackLast - next.delay; if (nextTime >= 0) { next.delay = 0; next.trackTime = nextTime + delta * next.timeScale; current.trackTime += currentDelta; setCurrent(i, next, true); while (next.mixingFrom != null) { next.mixTime += currentDelta; next = next.mixingFrom; } continue; } } else { // Clear the track when there is no next entry, the track end time is reached, and there is // no mixingFrom. if (current.trackLast >= current.trackEnd && current.mixingFrom == null) { tracks.set(i, null); queue.end(current); disposeNext(current); continue; } } updateMixingFrom(current, delta); current.trackTime += currentDelta; } queue.drain(); }
/** @param last May be null. */ private TrackEntry trackEntry( int trackIndex, Animation animation, boolean loop, TrackEntry last) { TrackEntry entry = trackEntryPool.obtain(); entry.trackIndex = trackIndex; entry.animation = animation; entry.loop = loop; entry.eventThreshold = 0; entry.attachmentThreshold = 0; entry.drawOrderThreshold = 0; entry.animationStart = 0; entry.animationEnd = animation.getDuration(); entry.animationLast = -1; entry.nextAnimationLast = -1; entry.delay = 0; entry.trackTime = 0; entry.trackLast = -1; entry.nextTrackLast = -1; entry.trackEnd = Float.MAX_VALUE; entry.timeScale = 1; entry.alpha = 1; entry.mixAlpha = 1; entry.mixTime = 0; entry.mixDuration = last == null ? 0 : data.getMix(last.animation, animation); return entry; }
/** * Sets an empty animation for a track, discarding any queued animations, and sets the track * entry's {@link TrackEntry#getMixDuration()}. * * <p>Mixing out is done by setting an empty animation. A mix duration of 0 still mixes out over * one frame. * * <p>To mix in, first set an empty animation and add an animation using {@link #addAnimation(int, * Animation, boolean, float)}, then set the {@link TrackEntry#setMixDuration(float)} on the * returned track entry. */ public TrackEntry setEmptyAnimation(int trackIndex, float mixDuration) { TrackEntry entry = setAnimation(trackIndex, emptyAnimation, false); entry.mixDuration = mixDuration; entry.trackEnd = mixDuration; return entry; }