unioil-loyalty-rn-app/ios/Pods/lottie-ios/Sources/Private/Utility/Primitives/CurveVertex.swift

185 lines
6.6 KiB
Swift

//
// CurveVertex.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/11/19.
//
import CoreGraphics
import Foundation
/// A single vertex with an in and out tangent
struct CurveVertex {
// MARK: Lifecycle
/// Initializes a curve point with absolute values
init(_ inTangent: CGPoint, _ point: CGPoint, _ outTangent: CGPoint) {
self.point = point
self.inTangent = inTangent
self.outTangent = outTangent
}
/// Initializes a curve point with relative values
init(point: CGPoint, inTangentRelative: CGPoint, outTangentRelative: CGPoint) {
self.point = point
inTangent = point.add(inTangentRelative)
outTangent = point.add(outTangentRelative)
}
/// Initializes a curve point with absolute values
init(point: CGPoint, inTangent: CGPoint, outTangent: CGPoint) {
self.point = point
self.inTangent = inTangent
self.outTangent = outTangent
}
// MARK: Internal
let point: CGPoint
let inTangent: CGPoint
let outTangent: CGPoint
var inTangentRelative: CGPoint {
inTangent.subtract(point)
}
var outTangentRelative: CGPoint {
outTangent.subtract(point)
}
func reversed() -> CurveVertex {
CurveVertex(point: point, inTangent: outTangent, outTangent: inTangent)
}
func translated(_ translation: CGPoint) -> CurveVertex {
CurveVertex(point: point + translation, inTangent: inTangent + translation, outTangent: outTangent + translation)
}
/// Trims a path defined by two Vertices at a specific position, from 0 to 1
///
/// The path can be visualized below.
///
/// F is fromVertex.
/// V is the vertex of the receiver.
/// P is the position from 0-1.
/// O is the outTangent of fromVertex.
/// F====O=========P=======I====V
///
/// After trimming the curve can be visualized below.
///
/// S is the returned Start vertex.
/// E is the returned End vertex.
/// T is the trim point.
/// TI and TO are the new tangents for the trimPoint
/// NO and NI are the new tangents for the startPoint and endPoints
/// S==NO=========TI==T==TO=======NI==E
func splitCurve(toVertex: CurveVertex, position: CGFloat) ->
(start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex)
{
/// If position is less than or equal to 0, trim at start.
if position <= 0 {
return (
start: CurveVertex(point: point, inTangentRelative: inTangentRelative, outTangentRelative: .zero),
trimPoint: CurveVertex(point: point, inTangentRelative: .zero, outTangentRelative: outTangentRelative),
end: toVertex)
}
/// If position is greater than or equal to 1, trim at end.
if position >= 1 {
return (
start: self,
trimPoint: CurveVertex(
point: toVertex.point,
inTangentRelative: toVertex.inTangentRelative,
outTangentRelative: .zero),
end: CurveVertex(
point: toVertex.point,
inTangentRelative: .zero,
outTangentRelative: toVertex.outTangentRelative))
}
if outTangentRelative.isZero, toVertex.inTangentRelative.isZero {
/// If both tangents are zero, then span to be trimmed is a straight line.
let trimPoint = point.interpolate(to: toVertex.point, amount: position)
return (
start: self,
trimPoint: CurveVertex(point: trimPoint, inTangentRelative: .zero, outTangentRelative: .zero),
end: toVertex)
}
/// Cutting by amount gives incorrect length....
/// One option is to cut by a stride until it gets close then edge it down.
/// Measuring a percentage of the spans does not equal the same as measuring a percentage of length.
/// This is where the historical trim path bugs come from.
let a = point.interpolate(to: outTangent, amount: position)
let b = outTangent.interpolate(to: toVertex.inTangent, amount: position)
let c = toVertex.inTangent.interpolate(to: toVertex.point, amount: position)
let d = a.interpolate(to: b, amount: position)
let e = b.interpolate(to: c, amount: position)
let f = d.interpolate(to: e, amount: position)
return (
start: CurveVertex(point: point, inTangent: inTangent, outTangent: a),
trimPoint: CurveVertex(point: f, inTangent: d, outTangent: e),
end: CurveVertex(point: toVertex.point, inTangent: c, outTangent: toVertex.outTangent))
}
/// Trims a curve of a known length to a specific length and returns the points.
///
/// There is not a performant yet accurate way to cut a curve to a specific length.
/// This calls splitCurve(toVertex: position:) to split the curve and then measures
/// the length of the new curve. The function then iterates through the samples,
/// adjusting the position of the cut for a more precise cut.
/// Usually a single iteration is enough to get within 0.5 points of the desired
/// length.
///
/// This function should probably live in PathElement, since it deals with curve
/// lengths.
func trimCurve(toVertex: CurveVertex, atLength: CGFloat, curveLength: CGFloat, maxSamples: Int, accuracy: CGFloat = 1) ->
(start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex)
{
var currentPosition = atLength / curveLength
var results = splitCurve(toVertex: toVertex, position: currentPosition)
if maxSamples == 0 {
return results
}
for _ in 1...maxSamples {
let length = results.start.distanceTo(results.trimPoint)
let lengthDiff = atLength - length
/// Check if length is correct.
if lengthDiff < accuracy {
return results
}
let diffPosition = max(min((currentPosition / length) * lengthDiff, currentPosition * 0.5), currentPosition * -0.5)
currentPosition = diffPosition + currentPosition
results = splitCurve(toVertex: toVertex, position: currentPosition)
}
return results
}
/// The distance from the receiver to the provided vertex.
///
/// For lines (zeroed tangents) the distance between the two points is measured.
/// For curves the curve is iterated over by sample count and the points are measured.
/// This is ~99% accurate at a sample count of 30
func distanceTo(_ toVertex: CurveVertex, sampleCount: Int = 25) -> CGFloat {
if outTangentRelative.isZero, toVertex.inTangentRelative.isZero {
/// Return a linear distance.
return point.distanceTo(toVertex.point)
}
var distance: CGFloat = 0
var previousPoint = point
for i in 0..<sampleCount {
let pointOnCurve = splitCurve(toVertex: toVertex, position: CGFloat(i) / CGFloat(sampleCount)).trimPoint
distance = distance + previousPoint.distanceTo(pointOnCurve.point)
previousPoint = pointOnCurve.point
}
distance = distance + previousPoint.distanceTo(toVertex.point)
return distance
}
}