/** * 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(); }
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 applyRotateTimeline( Timeline timeline, Skeleton skeleton, float time, float alpha, boolean setupPose, float[] timelinesRotation, int i, boolean firstFrame) { if (firstFrame) timelinesRotation[i] = 0; if (alpha == 1) { timeline.apply(skeleton, 0, time, null, 1, setupPose, false); return; } RotateTimeline rotateTimeline = (RotateTimeline) timeline; Bone bone = skeleton.bones.get(rotateTimeline.boneIndex); float[] frames = rotateTimeline.frames; if (time < frames[0]) { // Time is before first frame. if (setupPose) bone.rotation = bone.data.rotation; return; } float r2; if (time >= frames[frames.length - ENTRIES]) // Time is after last frame. r2 = bone.data.rotation + frames[frames.length + PREV_ROTATION]; else { // Interpolate between the previous frame and the current frame. int frame = Animation.binarySearch(frames, time, ENTRIES); float prevRotation = frames[frame + PREV_ROTATION]; float frameTime = frames[frame]; float percent = rotateTimeline.getCurvePercent( (frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); r2 = frames[frame + ROTATION] - prevRotation; r2 -= (16384 - (int) (16384.499999999996 - r2 / 360)) * 360; r2 = prevRotation + r2 * percent + bone.data.rotation; r2 -= (16384 - (int) (16384.499999999996 - r2 / 360)) * 360; } // Mix between rotations using the direction of the shortest route on the first frame while // detecting crosses. float r1 = setupPose ? bone.data.rotation : bone.rotation; float total, diff = r2 - r1; if (diff == 0) total = timelinesRotation[i]; else { diff -= (16384 - (int) (16384.499999999996 - diff / 360)) * 360; float lastTotal, lastDiff; if (firstFrame) { lastTotal = 0; lastDiff = diff; } else { lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. lastDiff = timelinesRotation[i + 1]; // Difference between bones. } boolean current = diff > 0, dir = lastTotal >= 0; // Detect cross at 0 (not 180). if (Math.signum(lastDiff) != Math.signum(diff) && Math.abs(lastDiff) <= 90) { // A cross after a 360 rotation is a loop. if (Math.abs(lastTotal) > 180) lastTotal += 360 * Math.signum(lastTotal); dir = current; } total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. if (dir != current) total += 360 * Math.signum(lastTotal); timelinesRotation[i] = total; } timelinesRotation[i + 1] = diff; r1 += total * alpha; bone.rotation = r1 - (16384 - (int) (16384.499999999996 - r1 / 360)) * 360; }