342 lines
6.9 KiB
Swift
342 lines
6.9 KiB
Swift
//
|
|
// Vector.swift
|
|
// lottie-swift
|
|
//
|
|
// Created by Brandon Withrow on 1/7/19.
|
|
//
|
|
|
|
import CoreGraphics
|
|
import Foundation
|
|
import QuartzCore
|
|
|
|
// MARK: - Vector1D + Codable
|
|
|
|
/// Single value container. Needed because lottie sometimes wraps a Double in an array.
|
|
extension Vector1D: Codable {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
/// Try to decode an array of doubles
|
|
do {
|
|
var container = try decoder.unkeyedContainer()
|
|
value = try container.decode(Double.self)
|
|
} catch {
|
|
value = try decoder.singleValueContainer().decode(Double.self)
|
|
}
|
|
}
|
|
|
|
// MARK: Public
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(value)
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
var cgFloatValue: CGFloat {
|
|
CGFloat(value)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Vector1D + AnyInitializable
|
|
|
|
extension Vector1D: AnyInitializable {
|
|
|
|
init(value: Any) throws {
|
|
if
|
|
let array = value as? [Double],
|
|
let double = array.first
|
|
{
|
|
self.value = double
|
|
} else if let double = value as? Double {
|
|
self.value = double
|
|
} else {
|
|
throw InitializableError.invalidInput
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension Double {
|
|
var vectorValue: Vector1D {
|
|
Vector1D(self)
|
|
}
|
|
}
|
|
|
|
// MARK: - Vector2D
|
|
|
|
/// Needed for decoding json {x: y:} to a CGPoint
|
|
public struct Vector2D: Codable, Hashable {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(x: Double, y: Double) {
|
|
self.x = x
|
|
self.y = y
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: Vector2D.CodingKeys.self)
|
|
|
|
do {
|
|
let xValue: [Double] = try container.decode([Double].self, forKey: .x)
|
|
x = xValue[0]
|
|
} catch {
|
|
x = try container.decode(Double.self, forKey: .x)
|
|
}
|
|
|
|
do {
|
|
let yValue: [Double] = try container.decode([Double].self, forKey: .y)
|
|
y = yValue[0]
|
|
} catch {
|
|
y = try container.decode(Double.self, forKey: .y)
|
|
}
|
|
}
|
|
|
|
// MARK: Public
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: Vector2D.CodingKeys.self)
|
|
try container.encode(x, forKey: .x)
|
|
try container.encode(y, forKey: .y)
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
var x: Double
|
|
var y: Double
|
|
|
|
var pointValue: CGPoint {
|
|
CGPoint(x: x, y: y)
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case x
|
|
case y
|
|
}
|
|
}
|
|
|
|
// MARK: AnyInitializable
|
|
|
|
extension Vector2D: AnyInitializable {
|
|
|
|
init(value: Any) throws {
|
|
guard let dictionary = value as? [String: Any] else {
|
|
throw InitializableError.invalidInput
|
|
}
|
|
|
|
if
|
|
let array = dictionary[CodingKeys.x.rawValue] as? [Double],
|
|
let double = array.first
|
|
{
|
|
x = double
|
|
} else if let double = dictionary[CodingKeys.x.rawValue] as? Double {
|
|
x = double
|
|
} else {
|
|
throw InitializableError.invalidInput
|
|
}
|
|
if
|
|
let array = dictionary[CodingKeys.y.rawValue] as? [Double],
|
|
let double = array.first
|
|
{
|
|
y = double
|
|
} else if let double = dictionary[CodingKeys.y.rawValue] as? Double {
|
|
y = double
|
|
} else {
|
|
throw InitializableError.invalidInput
|
|
}
|
|
}
|
|
}
|
|
|
|
extension CGPoint {
|
|
var vector2dValue: Vector2D {
|
|
Vector2D(x: Double(x), y: Double(y))
|
|
}
|
|
}
|
|
|
|
// MARK: - Vector3D + Codable
|
|
|
|
/// A three dimensional vector.
|
|
/// These vectors are encoded and decoded from [Double]
|
|
|
|
extension Vector3D: Codable {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init(x: CGFloat, y: CGFloat, z: CGFloat) {
|
|
self.x = Double(x)
|
|
self.y = Double(y)
|
|
self.z = Double(z)
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
var container = try decoder.unkeyedContainer()
|
|
|
|
if !container.isAtEnd {
|
|
x = try container.decode(Double.self)
|
|
} else {
|
|
x = 0
|
|
}
|
|
|
|
if !container.isAtEnd {
|
|
y = try container.decode(Double.self)
|
|
} else {
|
|
y = 0
|
|
}
|
|
|
|
if !container.isAtEnd {
|
|
z = try container.decode(Double.self)
|
|
} else {
|
|
z = 0
|
|
}
|
|
}
|
|
|
|
// MARK: Public
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.unkeyedContainer()
|
|
try container.encode(x)
|
|
try container.encode(y)
|
|
try container.encode(z)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Vector3D + AnyInitializable
|
|
|
|
extension Vector3D: AnyInitializable {
|
|
|
|
init(value: Any) throws {
|
|
guard var array = value as? [Double] else {
|
|
throw InitializableError.invalidInput
|
|
}
|
|
x = array.count > 0 ? array.removeFirst() : 0
|
|
y = array.count > 0 ? array.removeFirst() : 0
|
|
z = array.count > 0 ? array.removeFirst() : 0
|
|
}
|
|
|
|
}
|
|
|
|
extension Vector3D {
|
|
public var pointValue: CGPoint {
|
|
CGPoint(x: x, y: y)
|
|
}
|
|
|
|
public var sizeValue: CGSize {
|
|
CGSize(width: x, height: y)
|
|
}
|
|
}
|
|
|
|
extension CGPoint {
|
|
var vector3dValue: Vector3D {
|
|
Vector3D(x: x, y: y, z: 0)
|
|
}
|
|
}
|
|
|
|
extension CGSize {
|
|
var vector3dValue: Vector3D {
|
|
Vector3D(x: width, y: height, z: 1)
|
|
}
|
|
}
|
|
|
|
extension CATransform3D {
|
|
|
|
static func makeSkew(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D {
|
|
let mCos = cos(skewAxis.toRadians())
|
|
let mSin = sin(skewAxis.toRadians())
|
|
let aTan = tan(skew.toRadians())
|
|
|
|
let transform1 = CATransform3D(
|
|
m11: mCos,
|
|
m12: mSin,
|
|
m13: 0,
|
|
m14: 0,
|
|
m21: -mSin,
|
|
m22: mCos,
|
|
m23: 0,
|
|
m24: 0,
|
|
m31: 0,
|
|
m32: 0,
|
|
m33: 1,
|
|
m34: 0,
|
|
m41: 0,
|
|
m42: 0,
|
|
m43: 0,
|
|
m44: 1)
|
|
|
|
let transform2 = CATransform3D(
|
|
m11: 1,
|
|
m12: 0,
|
|
m13: 0,
|
|
m14: 0,
|
|
m21: aTan,
|
|
m22: 1,
|
|
m23: 0,
|
|
m24: 0,
|
|
m31: 0,
|
|
m32: 0,
|
|
m33: 1,
|
|
m34: 0,
|
|
m41: 0,
|
|
m42: 0,
|
|
m43: 0,
|
|
m44: 1)
|
|
|
|
let transform3 = CATransform3D(
|
|
m11: mCos,
|
|
m12: -mSin,
|
|
m13: 0,
|
|
m14: 0,
|
|
m21: mSin,
|
|
m22: mCos,
|
|
m23: 0,
|
|
m24: 0,
|
|
m31: 0,
|
|
m32: 0,
|
|
m33: 1,
|
|
m34: 0,
|
|
m41: 0,
|
|
m42: 0,
|
|
m43: 0,
|
|
m44: 1)
|
|
return CATransform3DConcat(transform3, CATransform3DConcat(transform2, transform1))
|
|
}
|
|
|
|
static func makeTransform(
|
|
anchor: CGPoint,
|
|
position: CGPoint,
|
|
scale: CGSize,
|
|
rotation: CGFloat,
|
|
skew: CGFloat?,
|
|
skewAxis: CGFloat?)
|
|
-> CATransform3D
|
|
{
|
|
if let skew = skew, let skewAxis = skewAxis {
|
|
return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).skewed(skew: -skew, skewAxis: skewAxis)
|
|
.scaled(scale * 0.01).translated(anchor * -1)
|
|
}
|
|
return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).scaled(scale * 0.01).translated(anchor * -1)
|
|
}
|
|
|
|
func rotated(_ degrees: CGFloat) -> CATransform3D {
|
|
CATransform3DRotate(self, degrees.toRadians(), 0, 0, 1)
|
|
}
|
|
|
|
func translated(_ translation: CGPoint) -> CATransform3D {
|
|
CATransform3DTranslate(self, translation.x, translation.y, 0)
|
|
}
|
|
|
|
func scaled(_ scale: CGSize) -> CATransform3D {
|
|
CATransform3DScale(self, scale.width, scale.height, 1)
|
|
}
|
|
|
|
func skewed(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D {
|
|
CATransform3DConcat(CATransform3D.makeSkew(skew: skew, skewAxis: skewAxis), self)
|
|
}
|
|
}
|