251 lines
6.7 KiB
Swift
251 lines
6.7 KiB
Swift
//
|
|
// KeyframeInterpolator.swift
|
|
// lottie-swift
|
|
//
|
|
// Created by Brandon Withrow on 1/15/19.
|
|
//
|
|
|
|
import CoreGraphics
|
|
import Foundation
|
|
|
|
// MARK: - KeyframeInterpolator
|
|
|
|
/// A value provider that produces a value at Time from a group of keyframes
|
|
final class KeyframeInterpolator<ValueType>: ValueProvider where ValueType: AnyInterpolatable {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(keyframes: ContiguousArray<Keyframe<ValueType>>) {
|
|
self.keyframes = keyframes
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
let keyframes: ContiguousArray<Keyframe<ValueType>>
|
|
|
|
var valueType: Any.Type {
|
|
ValueType.self
|
|
}
|
|
|
|
var storage: ValueProviderStorage<ValueType> {
|
|
.closure { [self] frame in
|
|
// First set the keyframe span for the frame.
|
|
updateSpanIndices(frame: frame)
|
|
lastUpdatedFrame = frame
|
|
// If only one keyframe return its value
|
|
let progress: CGFloat
|
|
let value: ValueType
|
|
|
|
if
|
|
let leading = leadingKeyframe,
|
|
let trailing = trailingKeyframe
|
|
{
|
|
/// We have leading and trailing keyframe.
|
|
progress = leading.interpolatedProgress(trailing, keyTime: frame)
|
|
value = leading.interpolate(to: trailing, progress: progress)
|
|
} else if let leading = leadingKeyframe {
|
|
progress = 0
|
|
value = leading.value
|
|
} else if let trailing = trailingKeyframe {
|
|
progress = 1
|
|
value = trailing.value
|
|
} else {
|
|
/// Satisfy the compiler.
|
|
progress = 0
|
|
value = keyframes[0].value
|
|
}
|
|
return value
|
|
}
|
|
}
|
|
|
|
/// Returns true to trigger a frame update for this interpolator.
|
|
///
|
|
/// An interpolator will be asked if it needs to update every frame.
|
|
/// If the interpolator needs updating it will be asked to compute its value for
|
|
/// the given frame.
|
|
///
|
|
/// Cases a keyframe should not be updated:
|
|
/// - If time is in span and leading keyframe is hold
|
|
/// - If time is after the last keyframe.
|
|
/// - If time is before the first keyframe
|
|
///
|
|
/// Cases for updating a keyframe:
|
|
/// - If time is in the span, and is not a hold
|
|
/// - If time is outside of the span, and there are more keyframes
|
|
/// - If a value delegate is set
|
|
/// - If leading and trailing are both nil.
|
|
func hasUpdate(frame: CGFloat) -> Bool {
|
|
if lastUpdatedFrame == nil {
|
|
return true
|
|
}
|
|
|
|
if
|
|
let leading = leadingKeyframe,
|
|
trailingKeyframe == nil,
|
|
leading.time < frame
|
|
{
|
|
/// Frame is after bounds of keyframes
|
|
return false
|
|
}
|
|
if
|
|
let trailing = trailingKeyframe,
|
|
leadingKeyframe == nil,
|
|
frame < trailing.time
|
|
{
|
|
/// Frame is before bounds of keyframes
|
|
return false
|
|
}
|
|
if
|
|
let leading = leadingKeyframe,
|
|
let trailing = trailingKeyframe,
|
|
leading.isHold,
|
|
leading.time < frame,
|
|
frame < trailing.time
|
|
{
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// MARK: Fileprivate
|
|
|
|
fileprivate var lastUpdatedFrame: CGFloat?
|
|
|
|
fileprivate var leadingIndex: Int? = nil
|
|
fileprivate var trailingIndex: Int? = nil
|
|
fileprivate var leadingKeyframe: Keyframe<ValueType>? = nil
|
|
fileprivate var trailingKeyframe: Keyframe<ValueType>? = nil
|
|
|
|
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
|
|
fileprivate func updateSpanIndices(frame: CGFloat) {
|
|
guard keyframes.count > 0 else {
|
|
leadingIndex = nil
|
|
trailingIndex = nil
|
|
leadingKeyframe = nil
|
|
trailingKeyframe = nil
|
|
return
|
|
}
|
|
|
|
// This function searches through the array to find the span of two keyframes
|
|
// that contain the current time.
|
|
//
|
|
// We could use Array.first(where:) but that would search through the entire array
|
|
// each frame.
|
|
// Instead we track the last used index and search either forwards or
|
|
// backwards from there. This reduces the iterations and complexity from
|
|
//
|
|
// O(n), where n is the length of the sequence to
|
|
// O(n), where n is the number of items after or before the last used index.
|
|
//
|
|
|
|
if keyframes.count == 1 {
|
|
/// Only one keyframe. Set it as first and move on.
|
|
leadingIndex = 0
|
|
trailingIndex = nil
|
|
leadingKeyframe = keyframes[0]
|
|
trailingKeyframe = nil
|
|
return
|
|
}
|
|
|
|
/// Sets the initial keyframes. This is often only needed for the first check.
|
|
if
|
|
leadingIndex == nil,
|
|
trailingIndex == nil
|
|
{
|
|
if frame < keyframes[0].time {
|
|
/// Time is before the first keyframe. Set it as the trailing.
|
|
trailingIndex = 0
|
|
} else {
|
|
/// Time is after the first keyframe. Set the keyframe and the trailing.
|
|
leadingIndex = 0
|
|
trailingIndex = 1
|
|
}
|
|
}
|
|
|
|
if
|
|
let currentTrailing = trailingIndex,
|
|
keyframes[currentTrailing].time <= frame
|
|
{
|
|
/// Time is after the current span. Iterate forward.
|
|
var newLeading = currentTrailing
|
|
var keyframeFound = false
|
|
while !keyframeFound {
|
|
leadingIndex = newLeading
|
|
trailingIndex = keyframes.validIndex(newLeading + 1)
|
|
|
|
guard let trailing = trailingIndex else {
|
|
/// We have reached the end of our keyframes. Time is after the last keyframe.
|
|
keyframeFound = true
|
|
continue
|
|
}
|
|
if frame < keyframes[trailing].time {
|
|
/// Keyframe in current span.
|
|
keyframeFound = true
|
|
continue
|
|
}
|
|
/// Advance the array.
|
|
newLeading = trailing
|
|
}
|
|
|
|
} else if
|
|
let currentLeading = leadingIndex,
|
|
frame < keyframes[currentLeading].time
|
|
{
|
|
/// Time is before the current span. Iterate backwards
|
|
var newTrailing = currentLeading
|
|
|
|
var keyframeFound = false
|
|
while !keyframeFound {
|
|
leadingIndex = keyframes.validIndex(newTrailing - 1)
|
|
trailingIndex = newTrailing
|
|
|
|
guard let leading = leadingIndex else {
|
|
/// We have reached the end of our keyframes. Time is after the last keyframe.
|
|
keyframeFound = true
|
|
continue
|
|
}
|
|
if keyframes[leading].time <= frame {
|
|
/// Keyframe in current span.
|
|
keyframeFound = true
|
|
continue
|
|
}
|
|
/// Step back
|
|
newTrailing = leading
|
|
}
|
|
}
|
|
if let keyFrame = leadingIndex {
|
|
leadingKeyframe = keyframes[keyFrame]
|
|
} else {
|
|
leadingKeyframe = nil
|
|
}
|
|
|
|
if let keyFrame = trailingIndex {
|
|
trailingKeyframe = keyframes[keyFrame]
|
|
} else {
|
|
trailingKeyframe = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Array {
|
|
|
|
fileprivate func validIndex(_ index: Int) -> Int? {
|
|
if 0 <= index, index < endIndex {
|
|
return index
|
|
}
|
|
return nil
|
|
}
|
|
|
|
}
|
|
|
|
extension ContiguousArray {
|
|
|
|
fileprivate func validIndex(_ index: Int) -> Int? {
|
|
if 0 <= index, index < endIndex {
|
|
return index
|
|
}
|
|
return nil
|
|
}
|
|
|
|
}
|