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

2 years ago
  1. // Created by Cal Stephens on 1/24/22.
  2. // Copyright © 2022 Airbnb Inc. All rights reserved.
  3. import CoreGraphics
  4. // MARK: - Interpolatable
  5. /// A type that can be interpolated between two values
  6. public protocol Interpolatable: AnyInterpolatable {
  7. /// Interpolates the `self` to the given number by `amount`.
  8. /// - Parameter to: The number to interpolate to.
  9. /// - Parameter amount: The amount to interpolate,
  10. /// relative to 0.0 (self) and 1.0 (to).
  11. /// `amount` can be greater than one and less than zero,
  12. /// and interpolation should not be clamped.
  13. ///
  14. /// ```
  15. /// let number = 5
  16. /// let interpolated = number.interpolateTo(10, amount: 0.5)
  17. /// print(interpolated) // 7.5
  18. /// ```
  19. ///
  20. /// ```
  21. /// let number = 5
  22. /// let interpolated = number.interpolateTo(10, amount: 1.5)
  23. /// print(interpolated) // 12.5
  24. /// ```
  25. func interpolate(to: Self, amount: CGFloat) -> Self
  26. }
  27. // MARK: - SpatialInterpolatable
  28. /// A type that can be interpolated between two values,
  29. /// additionally using optional `spatialOutTangent` and `spatialInTangent` values.
  30. /// - If your implementation doesn't use the `spatialOutTangent` and `spatialInTangent`
  31. /// parameters, prefer implementing the simpler `Interpolatable` protocol.
  32. public protocol SpatialInterpolatable: AnyInterpolatable {
  33. /// Interpolates the `self` to the given number by `amount`.
  34. /// - Parameter to: The number to interpolate to.
  35. /// - Parameter amount: The amount to interpolate,
  36. /// relative to 0.0 (self) and 1.0 (to).
  37. /// `amount` can be greater than one and less than zero,
  38. /// and interpolation should not be clamped.
  39. func interpolate(
  40. to: Self,
  41. amount: CGFloat,
  42. spatialOutTangent: CGPoint?,
  43. spatialInTangent: CGPoint?)
  44. -> Self
  45. }
  46. // MARK: - AnyInterpolatable
  47. /// The base protocol that is implemented by both `Interpolatable` and `SpatialInterpolatable`
  48. /// Types should not directly implement this protocol.
  49. public protocol AnyInterpolatable {
  50. /// Interpolates by calling either `Interpolatable.interpolate`
  51. /// or `SpatialInterpolatable.interpolate`.
  52. /// Should not be implemented or called by consumers.
  53. func _interpolate(
  54. to: Self,
  55. amount: CGFloat,
  56. spatialOutTangent: CGPoint?,
  57. spatialInTangent: CGPoint?)
  58. -> Self
  59. }
  60. extension Interpolatable {
  61. public func _interpolate(
  62. to: Self,
  63. amount: CGFloat,
  64. spatialOutTangent _: CGPoint?,
  65. spatialInTangent _: CGPoint?)
  66. -> Self
  67. {
  68. interpolate(to: to, amount: amount)
  69. }
  70. }
  71. extension SpatialInterpolatable {
  72. /// Helper that interpolates this `SpatialInterpolatable`
  73. /// with `nil` spatial in/out tangents
  74. public func interpolate(to: Self, amount: CGFloat) -> Self {
  75. interpolate(
  76. to: to,
  77. amount: amount,
  78. spatialOutTangent: nil,
  79. spatialInTangent: nil)
  80. }
  81. public func _interpolate(
  82. to: Self,
  83. amount: CGFloat,
  84. spatialOutTangent: CGPoint?,
  85. spatialInTangent: CGPoint?)
  86. -> Self
  87. {
  88. interpolate(
  89. to: to,
  90. amount: amount,
  91. spatialOutTangent: spatialOutTangent,
  92. spatialInTangent: spatialInTangent)
  93. }
  94. }
  95. // MARK: - Double + Interpolatable
  96. extension Double: Interpolatable { }
  97. // MARK: - CGFloat + Interpolatable
  98. extension CGFloat: Interpolatable { }
  99. // MARK: - Float + Interpolatable
  100. extension Float: Interpolatable { }
  101. extension Interpolatable where Self: BinaryFloatingPoint {
  102. public func interpolate(to: Self, amount: CGFloat) -> Self {
  103. self + ((to - self) * Self(amount))
  104. }
  105. }
  106. // MARK: - CGRect + Interpolatable
  107. extension CGRect: Interpolatable {
  108. public func interpolate(to: CGRect, amount: CGFloat) -> CGRect {
  109. CGRect(
  110. x: origin.x.interpolate(to: to.origin.x, amount: amount),
  111. y: origin.y.interpolate(to: to.origin.y, amount: amount),
  112. width: width.interpolate(to: to.width, amount: amount),
  113. height: height.interpolate(to: to.height, amount: amount))
  114. }
  115. }
  116. // MARK: - CGSize + Interpolatable
  117. extension CGSize: Interpolatable {
  118. public func interpolate(to: CGSize, amount: CGFloat) -> CGSize {
  119. CGSize(
  120. width: width.interpolate(to: to.width, amount: amount),
  121. height: height.interpolate(to: to.height, amount: amount))
  122. }
  123. }
  124. // MARK: - CGPoint + SpatialInterpolatable
  125. extension CGPoint: SpatialInterpolatable {
  126. public func interpolate(
  127. to: CGPoint,
  128. amount: CGFloat,
  129. spatialOutTangent: CGPoint?,
  130. spatialInTangent: CGPoint?)
  131. -> CGPoint
  132. {
  133. guard
  134. let outTan = spatialOutTangent,
  135. let inTan = spatialInTangent
  136. else {
  137. return CGPoint(
  138. x: x.interpolate(to: to.x, amount: amount),
  139. y: y.interpolate(to: to.y, amount: amount))
  140. }
  141. let cp1 = self + outTan
  142. let cp2 = to + inTan
  143. return interpolate(to, outTangent: cp1, inTangent: cp2, amount: amount)
  144. }
  145. }
  146. // MARK: - Color + Interpolatable
  147. extension Color: Interpolatable {
  148. public func interpolate(to: Color, amount: CGFloat) -> Color {
  149. Color(
  150. r: r.interpolate(to: to.r, amount: amount),
  151. g: g.interpolate(to: to.g, amount: amount),
  152. b: b.interpolate(to: to.b, amount: amount),
  153. a: a.interpolate(to: to.a, amount: amount))
  154. }
  155. }
  156. // MARK: - Vector1D + Interpolatable
  157. extension Vector1D: Interpolatable {
  158. public func interpolate(to: Vector1D, amount: CGFloat) -> Vector1D {
  159. value.interpolate(to: to.value, amount: amount).vectorValue
  160. }
  161. }
  162. // MARK: - Vector2D + SpatialInterpolatable
  163. extension Vector2D: SpatialInterpolatable {
  164. public func interpolate(
  165. to: Vector2D,
  166. amount: CGFloat,
  167. spatialOutTangent: CGPoint?,
  168. spatialInTangent: CGPoint?)
  169. -> Vector2D
  170. {
  171. pointValue.interpolate(
  172. to: to.pointValue,
  173. amount: amount,
  174. spatialOutTangent: spatialOutTangent,
  175. spatialInTangent: spatialInTangent)
  176. .vector2dValue
  177. }
  178. }
  179. // MARK: - Vector3D + SpatialInterpolatable
  180. extension Vector3D: SpatialInterpolatable {
  181. public func interpolate(
  182. to: Vector3D,
  183. amount: CGFloat,
  184. spatialOutTangent: CGPoint?,
  185. spatialInTangent: CGPoint?)
  186. -> Vector3D
  187. {
  188. if spatialInTangent != nil || spatialOutTangent != nil {
  189. // TODO Support third dimension spatial interpolation
  190. let point = pointValue.interpolate(
  191. to: to.pointValue,
  192. amount: amount,
  193. spatialOutTangent: spatialOutTangent,
  194. spatialInTangent: spatialInTangent)
  195. return Vector3D(
  196. x: point.x,
  197. y: point.y,
  198. z: CGFloat(z.interpolate(to: to.z, amount: amount)))
  199. }
  200. return Vector3D(
  201. x: x.interpolate(to: to.x, amount: amount),
  202. y: y.interpolate(to: to.y, amount: amount),
  203. z: z.interpolate(to: to.z, amount: amount))
  204. }
  205. }
  206. // MARK: - Array + Interpolatable, AnyInterpolatable
  207. extension Array: Interpolatable, AnyInterpolatable where Element: Interpolatable {
  208. public func interpolate(to: [Element], amount: CGFloat) -> [Element] {
  209. LottieLogger.shared.assert(
  210. count == to.count,
  211. "When interpolating Arrays, both array sound have the same element count.")
  212. return zip(self, to).map { lhs, rhs in
  213. lhs.interpolate(to: rhs, amount: amount)
  214. }
  215. }
  216. }