1/* 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7 8#import <ABI48_0_0React/ABI48_0_0RCTSpringAnimation.h> 9 10#import <UIKit/UIKit.h> 11 12#import <ABI48_0_0React/ABI48_0_0RCTConvert.h> 13#import <ABI48_0_0React/ABI48_0_0RCTDefines.h> 14 15#import <ABI48_0_0React/ABI48_0_0RCTAnimationUtils.h> 16#import <ABI48_0_0React/ABI48_0_0RCTValueAnimatedNode.h> 17 18@interface ABI48_0_0RCTSpringAnimation () 19 20@property (nonatomic, strong) NSNumber *animationId; 21@property (nonatomic, strong) ABI48_0_0RCTValueAnimatedNode *valueNode; 22@property (nonatomic, assign) BOOL animationHasBegun; 23@property (nonatomic, assign) BOOL animationHasFinished; 24 25@end 26 27const NSTimeInterval ABI48_0_0MAX_DELTA_TIME = 0.064; 28 29@implementation ABI48_0_0RCTSpringAnimation { 30 CGFloat _toValue; 31 CGFloat _fromValue; 32 BOOL _overshootClamping; 33 CGFloat _restDisplacementThreshold; 34 CGFloat _restSpeedThreshold; 35 CGFloat _stiffness; 36 CGFloat _damping; 37 CGFloat _mass; 38 CGFloat _initialVelocity; 39 NSTimeInterval _animationStartTime; 40 NSTimeInterval _animationCurrentTime; 41 ABI48_0_0RCTResponseSenderBlock _callback; 42 43 CGFloat _lastPosition; 44 CGFloat _lastVelocity; 45 46 NSInteger _iterations; 47 NSInteger _currentLoop; 48 49 NSTimeInterval _t; // Current time (startTime + dt) 50} 51 52- (instancetype)initWithId:(NSNumber *)animationId 53 config:(NSDictionary *)config 54 forNode:(ABI48_0_0RCTValueAnimatedNode *)valueNode 55 callBack:(nullable ABI48_0_0RCTResponseSenderBlock)callback 56{ 57 if ((self = [super init])) { 58 _animationId = animationId; 59 _lastPosition = valueNode.value; 60 _valueNode = valueNode; 61 _lastVelocity = [ABI48_0_0RCTConvert CGFloat:config[@"initialVelocity"]]; 62 _callback = [callback copy]; 63 [self resetAnimationConfig:config]; 64 } 65 return self; 66} 67 68- (void)resetAnimationConfig:(NSDictionary *)config 69{ 70 NSNumber *iterations = [ABI48_0_0RCTConvert NSNumber:config[@"iterations"]] ?: @1; 71 _toValue = [ABI48_0_0RCTConvert CGFloat:config[@"toValue"]]; 72 _overshootClamping = [ABI48_0_0RCTConvert BOOL:config[@"overshootClamping"]]; 73 _restDisplacementThreshold = [ABI48_0_0RCTConvert CGFloat:config[@"restDisplacementThreshold"]]; 74 _restSpeedThreshold = [ABI48_0_0RCTConvert CGFloat:config[@"restSpeedThreshold"]]; 75 _stiffness = [ABI48_0_0RCTConvert CGFloat:config[@"stiffness"]]; 76 _damping = [ABI48_0_0RCTConvert CGFloat:config[@"damping"]]; 77 _mass = [ABI48_0_0RCTConvert CGFloat:config[@"mass"]]; 78 _initialVelocity = _lastVelocity; 79 _fromValue = _lastPosition; 80 _fromValue = _lastPosition; 81 _lastVelocity = _initialVelocity; 82 _animationHasFinished = iterations.integerValue == 0; 83 _iterations = iterations.integerValue; 84 _currentLoop = 1; 85 _animationStartTime = _animationCurrentTime = -1; 86 _animationHasBegun = YES; 87} 88 89ABI48_0_0RCT_NOT_IMPLEMENTED(-(instancetype)init) 90 91- (void)startAnimation 92{ 93 _animationStartTime = _animationCurrentTime = -1; 94 _animationHasBegun = YES; 95} 96 97- (void)stopAnimation 98{ 99 _valueNode = nil; 100 if (_callback) { 101 _callback(@[ @{@"finished" : @(_animationHasFinished)} ]); 102 } 103} 104 105- (void)stepAnimationWithTime:(NSTimeInterval)currentTime 106{ 107 if (!_animationHasBegun || _animationHasFinished) { 108 // Animation has not begun or animation has already finished. 109 return; 110 } 111 112 // calculate delta time 113 if (_animationStartTime == -1) { 114 _t = 0.0; 115 _animationStartTime = currentTime; 116 } else { 117 // Handle frame drops, and only advance dt by a max of ABI48_0_0MAX_DELTA_TIME 118 NSTimeInterval deltaTime = MIN(ABI48_0_0MAX_DELTA_TIME, currentTime - _animationCurrentTime); 119 _t = _t + deltaTime / ABI48_0_0RCTAnimationDragCoefficient(); 120 } 121 122 // store the timestamp 123 _animationCurrentTime = currentTime; 124 125 CGFloat c = _damping; 126 CGFloat m = _mass; 127 CGFloat k = _stiffness; 128 CGFloat v0 = -_initialVelocity; 129 130 CGFloat zeta = c / (2 * sqrtf(k * m)); 131 CGFloat omega0 = sqrtf(k / m); 132 CGFloat omega1 = omega0 * sqrtf(1.0 - (zeta * zeta)); 133 CGFloat x0 = _toValue - _fromValue; 134 135 CGFloat position; 136 CGFloat velocity; 137 if (zeta < 1) { 138 // Under damped 139 CGFloat envelope = expf(-zeta * omega0 * _t); 140 position = _toValue - envelope * ((v0 + zeta * omega0 * x0) / omega1 * sinf(omega1 * _t) + x0 * cosf(omega1 * _t)); 141 // This looks crazy -- it's actually just the derivative of the 142 // oscillation function 143 velocity = 144 zeta * omega0 * envelope * (sinf(omega1 * _t) * (v0 + zeta * omega0 * x0) / omega1 + x0 * cosf(omega1 * _t)) - 145 envelope * (cosf(omega1 * _t) * (v0 + zeta * omega0 * x0) - omega1 * x0 * sinf(omega1 * _t)); 146 } else { 147 CGFloat envelope = expf(-omega0 * _t); 148 position = _toValue - envelope * (x0 + (v0 + omega0 * x0) * _t); 149 velocity = envelope * (v0 * (_t * omega0 - 1) + _t * x0 * (omega0 * omega0)); 150 } 151 152 _lastPosition = position; 153 _lastVelocity = velocity; 154 155 [self onUpdate:position]; 156 157 // Conditions for stopping the spring animation 158 BOOL isOvershooting = NO; 159 if (_overshootClamping && _stiffness != 0) { 160 if (_fromValue < _toValue) { 161 isOvershooting = position > _toValue; 162 } else { 163 isOvershooting = position < _toValue; 164 } 165 } 166 BOOL isVelocity = ABS(velocity) <= _restSpeedThreshold; 167 BOOL isDisplacement = YES; 168 if (_stiffness != 0) { 169 isDisplacement = ABS(_toValue - position) <= _restDisplacementThreshold; 170 } 171 172 if (isOvershooting || (isVelocity && isDisplacement)) { 173 if (_stiffness != 0) { 174 // Ensure that we end up with a round value 175 if (_animationHasFinished) { 176 return; 177 } 178 [self onUpdate:_toValue]; 179 } 180 181 if (_iterations == -1 || _currentLoop < _iterations) { 182 _lastPosition = _fromValue; 183 _lastVelocity = _initialVelocity; 184 // Set _animationStartTime to -1 to reset instance variables on the next animation step. 185 _animationStartTime = -1; 186 _currentLoop++; 187 [self onUpdate:_fromValue]; 188 } else { 189 _animationHasFinished = YES; 190 } 191 } 192} 193 194- (void)onUpdate:(CGFloat)outputValue 195{ 196 _valueNode.value = outputValue; 197 [_valueNode setNeedsUpdate]; 198} 199 200@end 201