Seth Willits was working on an interpolation protocol that would allow conforming constructs to interpolate from one value to another, regardless of their underlying types. It needed to work for `CGFloat`

as well as `Double`

, and be decomposable and useful for `CGPoint`

and `CGRect`

as well as any custom Swift `Polygon`

struct.

This created interesting challenges, as some of the most fundamental animation curves use exponentiation, which is not built into the Swift standard library. So I turned to `pow`

, which is only defined for float and double:

public func powf(_: Float, _: Float) -> Float public func pow(_: Double, _: Double) -> Double

In the past, I’ve created horrible bridging solution that permits migration from most floating point values to double. It’s not beautiful but I decided to pull it out from my toolkit and introduce it to this problem.

What I did was to build an extension on `BinaryFloatingPoint`

that returned an interpolated value along a styled curve. My hack let me create a single `interpolate`

method that applied across floating-point. I bridged to `Double`

to use the Darwin `pow`

function:

There’s an interesting `CGFloat`

bug in play here, where the value history does not display as a graph but the numbers are right and work properly during animation.

Here’s the code. Shout out your improvements and alternatives.

## 9 Comments

Hi,

I’d have written the first extension as:

extension BinaryFloatingPoint {

public var doubleValue: Double {

switch self {

case let value as Double: return value

case let value as Float: return Double(value)

case let value as Float80: return Double(value)

case let value as CGFloat: return Double(value)

default: fatalError("Unsupported floating point type")

}

}

}

Hi Erica,

The following rewrite of your code moves the curve functions to the InterpolationCurve enum, where I think they conceptually belong, rather than in an extension on a numeric protocol. An additional benefit is that it doesn’t need your extension on BinaryFloatingPoint that provides a doubleValue property.

import Darwin // for pow(_:_:)

public enum InterpolationCurve {

case linear, easeIn, easeOut, easeInOut

public func f(_ x: Double) -> Double {

switch self {

case .linear:

return x

case .easeIn:

return pow(x, 3)

case .easeOut:

return 1 - pow(1 - x, 3)

case .easeInOut where x Self {

return self + (other - self) * Self(curve.f(fraction))

}

}

for fraction in stride(from: 0.0, through: 1.0, by: 0.05) {

0.0.interpolate(to: 1.0, by: fraction, of: .linear)

0.0.interpolate(to: 1.0, by: fraction, of: .easeIn)

0.0.interpolate(to: 1.0, by: fraction, of: .easeOut)

0.0.interpolate(to: 1.0, by: fraction, of: .easeInOut)

}

import CoreGraphics // for CGPoint

public protocol Interpolating {

func interpolate(to other: Self, by fraction: Double, of curve: InterpolationCurve) -> Self

}

extension CGPoint: Interpolating {

public func interpolate(to other: CGPoint, by fraction: Double, of curve: InterpolationCurve) -> CGPoint {

return CGPoint(

x: self.x.interpolate(to: other.x, by: fraction, of: curve),

y: self.y.interpolate(to: other.y, by: fraction, of: curve)

)

}

}

let p1 = CGPoint.zero

let p2 = CGPoint(x: 1, y: 1)

for fraction in stride(from: 0.0, through: 1.0, by: 0.05) {

p1.interpolate(to: p2, by: fraction, of: .easeInOut)

}

The blog somehow mangled my code (besides the annoying double-spacing it creates). I’ll try reposting the InterpolatingCurve enum.

public enum InterpolationCurve {

case linear, easeIn, easeOut, easeInOut

public func f(_ x: Double) -> Double {

switch self {

case .linear:

return x

case .easeIn:

return pow(x, 3)

case .easeOut:

return 1 - pow(1 - x, 3)

case .easeInOut where x < 0.5:

return 0.5 * pow(2 * x , 3)

case .easeInOut:

return 1 - 0.5 * pow(2 * (1 - x), 3)

}

}

}

It’s OK now. 😀

Arrrrrggghhh! Now I see there are other lines missing in my first attempt. I’ll try posting the whole thing again.

import Darwin // for pow(_:_:)

public enum InterpolationCurve {

case linear, easeIn, easeOut, easeInOut

public func f(_ x: Double) -> Double {

switch self {

case .linear:

return x

case .easeIn:

return pow(x, 3)

case .easeOut:

return 1 - pow(1 - x, 3)

case .easeInOut where x Self {

return self + (other - self) * Self(curve.f(fraction))

}

}

for fraction in stride(from: 0.0, through: 1.0, by: 0.05) {

0.0.interpolate(to: 1.0, by: fraction, of: .linear)

0.0.interpolate(to: 1.0, by: fraction, of: .easeIn)

0.0.interpolate(to: 1.0, by: fraction, of: .easeOut)

0.0.interpolate(to: 1.0, by: fraction, of: .easeInOut)

}

import CoreGraphics // for CGPoint

public protocol Interpolating {

func interpolate(to other: Self, by fraction: Double, of curve: InterpolationCurve) -> Self

}

extension CGPoint: Interpolating {

public func interpolate(to other: CGPoint, by fraction: Double, of curve: InterpolationCurve) -> CGPoint {

return CGPoint(

x: self.x.interpolate(to: other.x, by: fraction, of: curve),

y: self.y.interpolate(to: other.y, by: fraction, of: curve)

)

}

}

let p1 = CGPoint.zero

let p2 = CGPoint(x: 1, y: 1)

for fraction in stride(from: 0.0, through: 1.0, by: 0.05) {

p1.interpolate(to: p2, by: fraction, of: .easeInOut)

}

Nope, the same lines as vanished the first time have vanished again. I’ll try a series of posts.

Part 1:

import Darwin // for pow(_:_:)

public enum InterpolationCurve {

case linear, easeIn, easeOut, easeInOut

public func f(_ x: Double) -> Double {

switch self {

case .linear:

return x

case .easeIn:

return pow(x, 3)

case .easeOut:

return 1 - pow(1 - x, 3)

case .easeInOut where x < 0.5:

return 0.5 * pow(2 * x , 3)

case .easeInOut:

return 1 - 0.5 * pow(2 * (1 - x), 3)

}

}

}

Part 2:

extension BinaryFloatingPoint {

public func interpolate(to other: Self, by fraction: Double, of curve: InterpolationCurve = .linear) -> Self {

return self + (other - self) * Self(curve.f(fraction))

}

}

for fraction in stride(from: 0.0, through: 1.0, by: 0.05) {

0.0.interpolate(to: 1.0, by: fraction, of: .linear)

0.0.interpolate(to: 1.0, by: fraction, of: .easeIn)

0.0.interpolate(to: 1.0, by: fraction, of: .easeOut)

0.0.interpolate(to: 1.0, by: fraction, of: .easeInOut)

}

Part 3:

import CoreGraphics // for CGPoint

public protocol Interpolating {

func interpolate(to other: Self, by fraction: Double, of curve: InterpolationCurve) -> Self

}

extension CGPoint: Interpolating {

public func interpolate(to other: CGPoint, by fraction: Double, of curve: InterpolationCurve) -> CGPoint {

return CGPoint(

x: self.x.interpolate(to: other.x, by: fraction, of: curve),

y: self.y.interpolate(to: other.y, by: fraction, of: curve)

)

}

}

let p1 = CGPoint.zero

let p2 = CGPoint(x: 1, y: 1)

for fraction in stride(from: 0.0, through: 1.0, by: 0.05) {

p1.interpolate(to: p2, by: fraction, of: .easeInOut)

}

Success at last (fingers crossed). Let me know if it doesn’t compile.