Skip to content

Commit

Permalink
Bridge approximatelyEqual to Swift Numerics' Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
b3ll committed Sep 4, 2023
1 parent 320b44f commit 3bb3e02
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 83 deletions.
8 changes: 4 additions & 4 deletions Sources/Motion/Animations/BasicAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
If the value is outside the range, or we can't determine what it should be, we'll just start from the beginning, since that's already an unexpected state.
*/
internal func attemptToUpdateAccumulatedTimeToMatchValue() {
if !_value.approximatelyEqual(to: _fromValue, epsilon: resolvingEpsilon) && !_value.approximatelyEqual(to: _toValue, epsilon: resolvingEpsilon) {
if !_value.isApproximatelyEqual(to: _fromValue, epsilon: resolvingEpsilon) && !_value.isApproximatelyEqual(to: _toValue, epsilon: resolvingEpsilon) {
// Try to find out where we are in the animation.
if let accumulatedTime = solveAccumulatedTime(easingFunction: easingFunction, range: &_range, value: &_value) {
self.accumulatedTime = accumulatedTime * duration
Expand Down Expand Up @@ -170,7 +170,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
internal func hasResolved<SIMDType: SupportedSIMD>(value: inout SIMDType, epsilon: inout SIMDType.EpsilonType, toValue: inout SIMDType) -> Bool {
/* Must Be Mirrored Below */

return value.approximatelyEqual(to: toValue, epsilon: epsilon)
return value.isApproximatelyEqual(to: toValue, epsilon: epsilon)
}
#else
@_specialize(kind: partial, where SIMDType == SIMD2<Float>)
Expand All @@ -190,7 +190,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
internal func hasResolved<SIMDType: SupportedSIMD>(value: inout SIMDType, epsilon: inout SIMDType.EpsilonType, toValue: inout SIMDType) -> Bool {
/* Must Be Mirrored Above */

return value.approximatelyEqual(to: toValue, epsilon: epsilon)
return value.isApproximatelyEqual(to: toValue, epsilon: epsilon)
}
#endif

Expand All @@ -209,7 +209,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
// MARK: - AnimationDriverObserver

public override func tick(frame: AnimationFrame) {
if duration.approximatelyEqual(to: 0.0) {
if duration.isApproximatelyEqual(to: 0.0) {
stop(resolveImmediately: true, postValueChanged: true)
return
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Motion/Animations/Functions/DecayFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public struct DecayFunction<Value: SIMDRepresentable> {
@_specialize(kind: partial, where SIMDType == SIMD64<Float>)
@_specialize(kind: partial, where SIMDType == SIMD64<Double>)
@inlinable public func roundSIMD<SIMDType: SupportedSIMD>(_ value: SIMDType, toNearest roundingFactor: SIMDType.Scalar) -> SIMDType {
if roundingFactor.approximatelyEqual(to: 0.0) {
if roundingFactor.isApproximatelyEqual(to: 0.0) {
return value
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Motion/Animations/Functions/EasingFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public struct EasingFunction<Value: SIMDRepresentable>: Hashable {
@inlinable internal func solveAccumulatedTimeSIMD(_ range: ClosedRange<Value.SIMDType>, value: Value.SIMDType) -> CFTimeInterval? {
guard let usableIndex = value.indices.first(where: { i -> Bool in
let fractionComplete = value[i] / (range.upperBound[i] - range.lowerBound[i])
return !(fractionComplete.approximatelyEqual(to: 0.0) || fractionComplete.approximatelyEqual(to: 1.0))
return !(fractionComplete.isApproximatelyEqual(to: 0.0) || fractionComplete.isApproximatelyEqual(to: 1.0))
}) else { return nil }

let fractionComplete = value[usableIndex] / (range.upperBound[usableIndex] - range.lowerBound[usableIndex])
Expand Down
4 changes: 2 additions & 2 deletions Sources/Motion/Animations/Functions/SpringFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public struct SpringFunction<Value: SIMDRepresentable> {
*/
public mutating func configure(response response_: Value.SIMDType.Scalar, dampingRatio: Value.SIMDType.Scalar) {
let response: Value.SIMDType.Scalar
if response_.approximatelyEqual(to: 0.0) {
if response_.isApproximatelyEqual(to: 0.0) {
// Having a zero response is unsupported, so we'll just supply an arbitrarily small value.
response = 0.0001
} else {
Expand Down Expand Up @@ -183,7 +183,7 @@ public struct SpringFunction<Value: SIMDRepresentable> {
// Derivative of the above analytic equation to get the speed of a spring. (velocity)
let d_x = velocity_x0_dampingRatio_w0 * cos_wD_dt - x0 * (wD * sin_wD_dt)
velocity = -(dampingRatio_w0 * x - decayEnvelope * d_x)
} else if dampingRatio.approximatelyEqual(to: 1.0) {
} else if dampingRatio.isApproximatelyEqual(to: 1.0) {
let decayEnvelope = SIMDType.Scalar.exp(-w0 * dt)

let A = x0
Expand Down
12 changes: 6 additions & 6 deletions Sources/Motion/Animations/SpringAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public final class SpringAnimation<Value: SIMDRepresentable>: ValueAnimation<Val
internal func hasResolved<SIMDType: SupportedSIMD>(value: inout SIMDType, epsilon: inout SIMDType.EpsilonType, toValue: inout SIMDType, velocity: inout SIMDType, previousValueDelta: inout SIMDType?) -> (valueResolved: Bool, velocityResolved: Bool) {
/* Must Be Mirrored Below */

let valueResolved = value.approximatelyEqual(to: toValue, epsilon: epsilon)
let valueResolved = value.isApproximatelyEqual(to: toValue, epsilon: epsilon)
if !valueResolved, !resolvesUponReachingToValue {
return (false, false)
}
Expand All @@ -239,7 +239,7 @@ public final class SpringAnimation<Value: SIMDRepresentable>: ValueAnimation<Val
For example: `SpringAnimation<CGFloat>` animating with `toValue` of 0.0, starting at 1.0, will have a value of ~0.1 on one frame, the next frame it could be either 0.0 or -0.1, in which case either the values have been reached or exeeded (sign changed from + to -).
*/
allValuesReachedOrExceededToValues = allValuesReachedOrExceededToValues &&
(previousValueDelta[index].sign != currentValueDelta[index].sign) || (value[index].approximatelyEqual(to: toValue[index], epsilon: 0.01))
(previousValueDelta[index].sign != currentValueDelta[index].sign) || (value[index].isApproximatelyEqual(to: toValue[index], epsilon: 0.01))

index += 1
}
Expand All @@ -250,7 +250,7 @@ public final class SpringAnimation<Value: SIMDRepresentable>: ValueAnimation<Val
return (hasReachedOrExceededToValue, true)
}

let velocityResolved = velocity.approximatelyEqual(to: .zero, epsilon: epsilon)
let velocityResolved = velocity.isApproximatelyEqual(to: .zero, epsilon: epsilon)
return (valueResolved, velocityResolved)
}
#else
Expand All @@ -271,7 +271,7 @@ public final class SpringAnimation<Value: SIMDRepresentable>: ValueAnimation<Val
internal func hasResolved<SIMDType: SupportedSIMD>(value: inout SIMDType, epsilon: inout SIMDType.EpsilonType, toValue: inout SIMDType, velocity: inout SIMDType, previousValueDelta: inout SIMDType?) -> (valueResolved: Bool, velocityResolved: Bool) {
/* Must Be Mirrored Above */

let valueResolved = value.approximatelyEqual(to: toValue, epsilon: epsilon)
let valueResolved = value.isApproximatelyEqual(to: toValue, epsilon: epsilon)
if !valueResolved, !resolvesUponReachingToValue {
return (false, false)
}
Expand All @@ -289,7 +289,7 @@ public final class SpringAnimation<Value: SIMDRepresentable>: ValueAnimation<Val
For example: `SpringAnimation<CGFloat>` animating with `toValue` of 0.0, starting at 1.0, will have a value of ~0.1 on one frame, the next frame it could be either 0.0 or -0.1, in which case either the values have been reached or exeeded (sign changed from + to -).
*/
allValuesReachedOrExceededToValues = allValuesReachedOrExceededToValues &&
(previousValueDelta[index].sign != currentValueDelta[index].sign) || (value[index].approximatelyEqual(to: toValue[index], epsilon: 0.01))
(previousValueDelta[index].sign != currentValueDelta[index].sign) || (value[index].isApproximatelyEqual(to: toValue[index], epsilon: 0.01))

index += 1
}
Expand All @@ -300,7 +300,7 @@ public final class SpringAnimation<Value: SIMDRepresentable>: ValueAnimation<Val
return (hasReachedOrExceededToValue, true)
}

let velocityResolved = velocity.approximatelyEqual(to: .zero, epsilon: epsilon)
let velocityResolved = velocity.isApproximatelyEqual(to: .zero, epsilon: epsilon)
return (valueResolved, velocityResolved)
}
#endif
Expand Down
13 changes: 8 additions & 5 deletions Sources/Motion/Protocols/EquatableEnough.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import CoreGraphics
import Foundation
import RealModule
import simd

// MARK: - FloatingPointInitializable
Expand Down Expand Up @@ -40,7 +41,7 @@ public protocol EquatableEnough: Comparable {
Declares whether or not something else is equal to `self` within a given tolerance.
(e.g. a floating point value that is equal to another floating point value within a given epsilon)
*/
func approximatelyEqual(to: Self, epsilon: EpsilonType) -> Bool
func isApproximatelyEqual(to: Self, epsilon: EpsilonType) -> Bool

}

Expand Down Expand Up @@ -85,9 +86,11 @@ public extension EquatableEnough where Self: FloatingPoint & FloatingPointInitia
/**
Declares whether or not something else is equal to `self` within a given tolerance.
(e.g. a floating point value that is equal to another floating point value within a given epsilon)
Bridges to Swift Numerics' `isApproximatelyEqual` (https://github.com/schwa/ApproximateEquality/blob/main/Sources/ApproximateEquality/ApproximateEquality.swift).
*/
@inlinable func approximatelyEqual(to other: Self, epsilon: Self = .epsilon) -> Bool {
return abs(self - other) < epsilon
@inlinable func isApproximatelyEqual(to other: Self, epsilon: Self = .epsilon) -> Bool {
return isApproximatelyEqual(to: other, absoluteTolerance: epsilon, relativeTolerance: .zero)
}

}
Expand Down Expand Up @@ -118,9 +121,9 @@ public extension EquatableEnough where Self: FloatingPoint & FloatingPointInitia
*/
extension SupportedSIMD where Self: EquatableEnough & Comparable, Scalar: SupportedScalar {

@inlinable public func approximatelyEqual(to other: Self, epsilon: Scalar = .epsilon) -> Bool {
@inlinable public func isApproximatelyEqual(to other: Self, epsilon: Scalar = .epsilon) -> Bool {
for i in 0..<indices.count {
let equal = self[i].approximatelyEqual(to: other[i], epsilon: epsilon)
let equal = self[i].isApproximatelyEqual(to: other[i], epsilon: epsilon)
if !equal {
return false
}
Expand Down
8 changes: 4 additions & 4 deletions Tests/MotionTests/BasicAnimationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ final class BasicAnimationTests: XCTestCase {
let range: ClosedRange<CGFloat> = 0.0...10.0

let startValue = easeIn.solveInterpolatedValue(range, fraction: 0.0)
XCTAssert(startValue.approximatelyEqual(to: range.lowerBound))
XCTAssert(startValue.isApproximatelyEqual(to: range.lowerBound))

let endValue = easeIn.solveInterpolatedValue(range, fraction: 1.0)
XCTAssert(endValue.approximatelyEqual(to: range.upperBound))
XCTAssert(endValue.isApproximatelyEqual(to: range.upperBound))
}

func testBasicAnimationStartStop() {
Expand Down Expand Up @@ -96,7 +96,7 @@ final class BasicAnimationTests: XCTestCase {

let timeAccumulatedDeterminedFromValue = basicAnimation.accumulatedTime

XCTAssertTrue(timeAccumulated.approximatelyEqual(to: timeAccumulatedDeterminedFromValue))
XCTAssertTrue(timeAccumulated.isApproximatelyEqual(to: timeAccumulatedDeterminedFromValue))

let expectBasicAnimationCompletionCalled = XCTestExpectation(description: "Basic animation completed")
basicAnimation.completion = {
Expand All @@ -121,7 +121,7 @@ final class BasicAnimationTests: XCTestCase {
XCTAssertEqual(keyframeAnimation.calculationMode, .discrete)
XCTAssertFalse(keyframeAnimation.values?.isEmpty ?? true)
XCTAssertFalse(keyframeAnimation.keyTimes?.isEmpty ?? true)
XCTAssertTrue(keyframeAnimation.duration.approximatelyEqual(to: 1.0))
XCTAssertTrue(keyframeAnimation.duration.isApproximatelyEqual(to: 1.0))
}

override class func tearDown() {
Expand Down
26 changes: 13 additions & 13 deletions Tests/MotionTests/DecayAnimationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,44 +82,44 @@ final class DecayAnimationTests: XCTestCase {
let decay = DecayAnimation<CGFloat>()
decay.velocity = 2000.0

XCTAssertTrue(decay.toValue.approximatelyEqual(to: 998.999))
XCTAssertTrue(decay._toValue.approximatelyEqual(to: SIMD2<Double>(998.999, 0.0)))
XCTAssertTrue(decay.toValue.isApproximatelyEqual(to: 998.999))
XCTAssertTrue(decay._toValue.isApproximatelyEqual(to: SIMD2<Double>(998.999, 0.0)))

let decay2 = DecayAnimation<CGPoint>()
decay2.velocity = CGPoint(x: 1000.0, y: 2000.0)

XCTAssertTrue(decay2.toValue.x.approximatelyEqual(to: 499.499) && decay2.toValue.y.approximatelyEqual(to: 998.999))
XCTAssertTrue(decay2._toValue.approximatelyEqual(to: SIMD2<Double>(499.499, 998.999)))
XCTAssertTrue(decay2.toValue.x.isApproximatelyEqual(to: 499.499) && decay2.toValue.y.isApproximatelyEqual(to: 998.999))
XCTAssertTrue(decay2._toValue.isApproximatelyEqual(to: SIMD2<Double>(499.499, 998.999)))
}

func testDecayVelocityCalculation() {
let decay = DecayAnimation<CGFloat>()
decay.toValue = 998.999

XCTAssertTrue(decay.velocity.approximatelyEqual(to: 2000.0, epsilon: 0.01))
XCTAssertTrue(decay._velocity.approximatelyEqual(to: SIMD2<Double>(2000.0, 0.0), epsilon: 0.01))
XCTAssertTrue(decay.velocity.isApproximatelyEqual(to: 2000.0, epsilon: 0.01))
XCTAssertTrue(decay._velocity.isApproximatelyEqual(to: SIMD2<Double>(2000.0, 0.0), epsilon: 0.01))

let decay2 = DecayAnimation<CGPoint>()
decay2.toValue = CGPoint(x: 499.499, y: 998.999)

XCTAssertTrue(decay2.velocity.x.approximatelyEqual(to: 1000.0, epsilon: 0.01) && decay2.velocity.y.approximatelyEqual(to: 2000.0, epsilon: 0.01))
XCTAssertTrue(decay2._velocity.approximatelyEqual(to: SIMD2<Double>(1000.0, 2000.0), epsilon: 0.01))
XCTAssertTrue(decay2.velocity.x.isApproximatelyEqual(to: 1000.0, epsilon: 0.01) && decay2.velocity.y.isApproximatelyEqual(to: 2000.0, epsilon: 0.01))
XCTAssertTrue(decay2._velocity.isApproximatelyEqual(to: SIMD2<Double>(1000.0, 2000.0), epsilon: 0.01))
}

func testDecayRoundingFactorApplication() {
let decay = DecayAnimation<CGFloat>()
decay.roundingFactor = 1.0
decay.velocity = 200.0

XCTAssertTrue(decay.velocity.approximatelyEqual(to: 200.2, epsilon: 0.001))
XCTAssertTrue(decay.toValue.approximatelyEqual(to: 100.0, epsilon: 0.001))
XCTAssertTrue(decay.velocity.isApproximatelyEqual(to: 200.2, epsilon: 0.001))
XCTAssertTrue(decay.toValue.isApproximatelyEqual(to: 100.0, epsilon: 0.001))

let decay2 = DecayAnimation<CGFloat>()
decay2.roundingFactor = 1.0 / 3.0 // i.e. 3x device
decay2.velocity = 200.90

XCTAssertTrue(decay2.velocity.approximatelyEqual(to: 200.867, epsilon: 0.001))
XCTAssertTrue(decay2.toValue.approximatelyEqual(to: 100.333, epsilon: 0.001))
XCTAssertTrue(decay2.velocity.isApproximatelyEqual(to: 200.867, epsilon: 0.001))
XCTAssertTrue(decay2.toValue.isApproximatelyEqual(to: 100.333, epsilon: 0.001))
}

// MARK: - CAKeyframeAnimationEmittable Tests
Expand All @@ -133,7 +133,7 @@ final class DecayAnimationTests: XCTestCase {
XCTAssertEqual(keyframeAnimation.calculationMode, .discrete)
XCTAssertFalse(keyframeAnimation.values?.isEmpty ?? true)
XCTAssertFalse(keyframeAnimation.keyTimes?.isEmpty ?? true)
XCTAssertTrue(keyframeAnimation.duration.approximatelyEqual(to: 4.166))
XCTAssertTrue(keyframeAnimation.duration.isApproximatelyEqual(to: 4.166))
}

override class func tearDown() {
Expand Down
Loading

0 comments on commit 3bb3e02

Please sign in to comment.