// // LOTRoundedRectAnimator.m // Lottie // // Created by brandon_withrow on 7/19/17. // Copyright © 2017 Airbnb. All rights reserved. // #import "LOTRoundedRectAnimator.h" #import "LOTPointInterpolator.h" #import "LOTNumberInterpolator.h" #import "CGGeometry+LOTAdditions.h" @implementation LOTRoundedRectAnimator { LOTPointInterpolator *_centerInterpolator; LOTPointInterpolator *_sizeInterpolator; LOTNumberInterpolator *_cornerRadiusInterpolator; BOOL _reversed; } - (instancetype _Nonnull )initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode shapeRectangle:(LOTShapeRectangle *_Nonnull)shapeRectangle { self = [super initWithInputNode:inputNode keyName:shapeRectangle.keyname]; if (self) { _centerInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeRectangle.position.keyframes]; _sizeInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeRectangle.size.keyframes]; _cornerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeRectangle.cornerRadius.keyframes]; _reversed = shapeRectangle.reversed; } return self; } - (NSDictionary *)valueInterpolators { return @{@"Size" : _sizeInterpolator, @"Position" : _centerInterpolator, @"Roundness" : _cornerRadiusInterpolator}; } - (BOOL)needsUpdateForFrame:(NSNumber *)frame { return [_centerInterpolator hasUpdateForFrame:frame] || [_sizeInterpolator hasUpdateForFrame:frame] || [_cornerRadiusInterpolator hasUpdateForFrame:frame]; } - (void)addCorner:(CGPoint)cornerPoint withRadius:(CGFloat)radius toPath:(LOTBezierPath *)path clockwise:(BOOL)clockwise { CGPoint currentPoint = path.currentPoint; CGFloat ellipseControlPointPercentage = 0.55228; if (cornerPoint.y == currentPoint.y) { // Moving east/west if (cornerPoint.x < currentPoint.x) { // Moving west CGPoint corner = CGPointMake(cornerPoint.x + radius, currentPoint.y); [path LOT_addLineToPoint:corner]; if (radius) { CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x, cornerPoint.y - radius) : CGPointMake(cornerPoint.x, cornerPoint.y + radius); CGPoint cp1 = CGPointMake(corner.x - (radius * ellipseControlPointPercentage), corner.y); CGPoint cp2 = (clockwise ? CGPointMake(curvePoint.x, curvePoint.y + (radius * ellipseControlPointPercentage)) : CGPointMake(curvePoint.x, curvePoint.y - (radius * ellipseControlPointPercentage))); [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2]; } } else { // Moving east CGPoint corner = CGPointMake(cornerPoint.x - radius, currentPoint.y); [path LOT_addLineToPoint:corner]; if (radius) { CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x, cornerPoint.y + radius) : CGPointMake(cornerPoint.x, cornerPoint.y - radius); CGPoint cp1 = CGPointMake(corner.x + (radius * ellipseControlPointPercentage), corner.y); CGPoint cp2 = (clockwise ? CGPointMake(curvePoint.x, curvePoint.y - (radius * ellipseControlPointPercentage)) : CGPointMake(curvePoint.x, curvePoint.y + (radius * ellipseControlPointPercentage))); [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2]; } } } else { // Moving North/South if (cornerPoint.y < currentPoint.y) { // Moving North CGPoint corner = CGPointMake(currentPoint.x, cornerPoint.y + radius); [path LOT_addLineToPoint:corner]; if (radius) { CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x + radius, cornerPoint.y) : CGPointMake(cornerPoint.x - radius, cornerPoint.y); CGPoint cp1 = CGPointMake(corner.x, corner.y - (radius * ellipseControlPointPercentage)); CGPoint cp2 = (clockwise ? CGPointMake(curvePoint.x - (radius * ellipseControlPointPercentage), curvePoint.y) : CGPointMake(curvePoint.x + (radius * ellipseControlPointPercentage), curvePoint.y)); [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2]; } } else { // moving south CGPoint corner = CGPointMake(currentPoint.x, cornerPoint.y - radius); [path LOT_addLineToPoint:corner]; if (radius) { CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x - radius, cornerPoint.y) : CGPointMake(cornerPoint.x + radius, cornerPoint.y); CGPoint cp1 = CGPointMake(corner.x, corner.y + (radius * ellipseControlPointPercentage)); CGPoint cp2 = (clockwise ? CGPointMake(curvePoint.x + (radius * ellipseControlPointPercentage), curvePoint.y) : CGPointMake(curvePoint.x - (radius * ellipseControlPointPercentage), curvePoint.y)); [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2]; } } } } - (void)performLocalUpdate { CGFloat cornerRadius = [_cornerRadiusInterpolator floatValueForFrame:self.currentFrame]; CGPoint size = [_sizeInterpolator pointValueForFrame:self.currentFrame]; CGPoint position = [_centerInterpolator pointValueForFrame:self.currentFrame]; CGFloat halfWidth = size.x / 2; CGFloat halfHeight = size.y / 2; CGRect rectFrame = CGRectMake(position.x - halfWidth, position.y - halfHeight, size.x, size.y); CGPoint topLeft = CGPointMake(CGRectGetMinX(rectFrame), CGRectGetMinY(rectFrame)); CGPoint topRight = CGPointMake(CGRectGetMaxX(rectFrame), CGRectGetMinY(rectFrame)); CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rectFrame), CGRectGetMaxY(rectFrame)); CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rectFrame), CGRectGetMaxY(rectFrame)); // UIBezierPath Draws rects from the top left corner, After Effects draws them from the top right. // Switching to manual drawing. CGFloat radius = MIN(MIN(halfWidth, halfHeight), cornerRadius); BOOL clockWise = !_reversed; LOTBezierPath *path1 = [[LOTBezierPath alloc] init]; path1.cacheLengths = self.pathShouldCacheLengths; CGPoint startPoint = (clockWise ? CGPointMake(topRight.x, topRight.y + radius) : CGPointMake(topRight.x - radius, topRight.y)); [path1 LOT_moveToPoint:startPoint]; if (clockWise) { [self addCorner:bottomRight withRadius:radius toPath:path1 clockwise:clockWise]; [self addCorner:bottomLeft withRadius:radius toPath:path1 clockwise:clockWise]; [self addCorner:topLeft withRadius:radius toPath:path1 clockwise:clockWise]; [self addCorner:topRight withRadius:radius toPath:path1 clockwise:clockWise]; } else { [self addCorner:topLeft withRadius:radius toPath:path1 clockwise:clockWise]; [self addCorner:bottomLeft withRadius:radius toPath:path1 clockwise:clockWise]; [self addCorner:bottomRight withRadius:radius toPath:path1 clockwise:clockWise]; [self addCorner:topRight withRadius:radius toPath:path1 clockwise:clockWise]; } [path1 LOT_closePath]; self.localPath = path1; } @end