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