You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

253 lines
7.0 KiB

// Created by Cal Stephens on 1/24/22.
// Copyright © 2022 Airbnb Inc. All rights reserved.
import CoreGraphics
// MARK: - Interpolatable
/// A type that can be interpolated between two values
public protocol Interpolatable: AnyInterpolatable {
/// Interpolates the `self` to the given number by `amount`.
/// - Parameter to: The number to interpolate to.
/// - Parameter amount: The amount to interpolate,
/// relative to 0.0 (self) and 1.0 (to).
/// `amount` can be greater than one and less than zero,
/// and interpolation should not be clamped.
///
/// ```
/// let number = 5
/// let interpolated = number.interpolateTo(10, amount: 0.5)
/// print(interpolated) // 7.5
/// ```
///
/// ```
/// let number = 5
/// let interpolated = number.interpolateTo(10, amount: 1.5)
/// print(interpolated) // 12.5
/// ```
func interpolate(to: Self, amount: CGFloat) -> Self
}
// MARK: - SpatialInterpolatable
/// A type that can be interpolated between two values,
/// additionally using optional `spatialOutTangent` and `spatialInTangent` values.
/// - If your implementation doesn't use the `spatialOutTangent` and `spatialInTangent`
/// parameters, prefer implementing the simpler `Interpolatable` protocol.
public protocol SpatialInterpolatable: AnyInterpolatable {
/// Interpolates the `self` to the given number by `amount`.
/// - Parameter to: The number to interpolate to.
/// - Parameter amount: The amount to interpolate,
/// relative to 0.0 (self) and 1.0 (to).
/// `amount` can be greater than one and less than zero,
/// and interpolation should not be clamped.
func interpolate(
to: Self,
amount: CGFloat,
spatialOutTangent: CGPoint?,
spatialInTangent: CGPoint?)
-> Self
}
// MARK: - AnyInterpolatable
/// The base protocol that is implemented by both `Interpolatable` and `SpatialInterpolatable`
/// Types should not directly implement this protocol.
public protocol AnyInterpolatable {
/// Interpolates by calling either `Interpolatable.interpolate`
/// or `SpatialInterpolatable.interpolate`.
/// Should not be implemented or called by consumers.
func _interpolate(
to: Self,
amount: CGFloat,
spatialOutTangent: CGPoint?,
spatialInTangent: CGPoint?)
-> Self
}
extension Interpolatable {
public func _interpolate(
to: Self,
amount: CGFloat,
spatialOutTangent _: CGPoint?,
spatialInTangent _: CGPoint?)
-> Self
{
interpolate(to: to, amount: amount)
}
}
extension SpatialInterpolatable {
/// Helper that interpolates this `SpatialInterpolatable`
/// with `nil` spatial in/out tangents
public func interpolate(to: Self, amount: CGFloat) -> Self {
interpolate(
to: to,
amount: amount,
spatialOutTangent: nil,
spatialInTangent: nil)
}
public func _interpolate(
to: Self,
amount: CGFloat,
spatialOutTangent: CGPoint?,
spatialInTangent: CGPoint?)
-> Self
{
interpolate(
to: to,
amount: amount,
spatialOutTangent: spatialOutTangent,
spatialInTangent: spatialInTangent)
}
}
// MARK: - Double + Interpolatable
extension Double: Interpolatable { }
// MARK: - CGFloat + Interpolatable
extension CGFloat: Interpolatable { }
// MARK: - Float + Interpolatable
extension Float: Interpolatable { }
extension Interpolatable where Self: BinaryFloatingPoint {
public func interpolate(to: Self, amount: CGFloat) -> Self {
self + ((to - self) * Self(amount))
}
}
// MARK: - CGRect + Interpolatable
extension CGRect: Interpolatable {
public func interpolate(to: CGRect, amount: CGFloat) -> CGRect {
CGRect(
x: origin.x.interpolate(to: to.origin.x, amount: amount),
y: origin.y.interpolate(to: to.origin.y, amount: amount),
width: width.interpolate(to: to.width, amount: amount),
height: height.interpolate(to: to.height, amount: amount))
}
}
// MARK: - CGSize + Interpolatable
extension CGSize: Interpolatable {
public func interpolate(to: CGSize, amount: CGFloat) -> CGSize {
CGSize(
width: width.interpolate(to: to.width, amount: amount),
height: height.interpolate(to: to.height, amount: amount))
}
}
// MARK: - CGPoint + SpatialInterpolatable
extension CGPoint: SpatialInterpolatable {
public func interpolate(
to: CGPoint,
amount: CGFloat,
spatialOutTangent: CGPoint?,
spatialInTangent: CGPoint?)
-> CGPoint
{
guard
let outTan = spatialOutTangent,
let inTan = spatialInTangent
else {
return CGPoint(
x: x.interpolate(to: to.x, amount: amount),
y: y.interpolate(to: to.y, amount: amount))
}
let cp1 = self + outTan
let cp2 = to + inTan
return interpolate(to, outTangent: cp1, inTangent: cp2, amount: amount)
}
}
// MARK: - Color + Interpolatable
extension Color: Interpolatable {
public func interpolate(to: Color, amount: CGFloat) -> Color {
Color(
r: r.interpolate(to: to.r, amount: amount),
g: g.interpolate(to: to.g, amount: amount),
b: b.interpolate(to: to.b, amount: amount),
a: a.interpolate(to: to.a, amount: amount))
}
}
// MARK: - Vector1D + Interpolatable
extension Vector1D: Interpolatable {
public func interpolate(to: Vector1D, amount: CGFloat) -> Vector1D {
value.interpolate(to: to.value, amount: amount).vectorValue
}
}
// MARK: - Vector2D + SpatialInterpolatable
extension Vector2D: SpatialInterpolatable {
public func interpolate(
to: Vector2D,
amount: CGFloat,
spatialOutTangent: CGPoint?,
spatialInTangent: CGPoint?)
-> Vector2D
{
pointValue.interpolate(
to: to.pointValue,
amount: amount,
spatialOutTangent: spatialOutTangent,
spatialInTangent: spatialInTangent)
.vector2dValue
}
}
// MARK: - Vector3D + SpatialInterpolatable
extension Vector3D: SpatialInterpolatable {
public func interpolate(
to: Vector3D,
amount: CGFloat,
spatialOutTangent: CGPoint?,
spatialInTangent: CGPoint?)
-> Vector3D
{
if spatialInTangent != nil || spatialOutTangent != nil {
// TODO Support third dimension spatial interpolation
let point = pointValue.interpolate(
to: to.pointValue,
amount: amount,
spatialOutTangent: spatialOutTangent,
spatialInTangent: spatialInTangent)
return Vector3D(
x: point.x,
y: point.y,
z: CGFloat(z.interpolate(to: to.z, amount: amount)))
}
return Vector3D(
x: x.interpolate(to: to.x, amount: amount),
y: y.interpolate(to: to.y, amount: amount),
z: z.interpolate(to: to.z, amount: amount))
}
}
// MARK: - Array + Interpolatable, AnyInterpolatable
extension Array: Interpolatable, AnyInterpolatable where Element: Interpolatable {
public func interpolate(to: [Element], amount: CGFloat) -> [Element] {
LottieLogger.shared.assert(
count == to.count,
"When interpolating Arrays, both array sound have the same element count.")
return zip(self, to).map { lhs, rhs in
lhs.interpolate(to: rhs, amount: amount)
}
}
}