Pods/lottie-ios/lottie-ios/Classes/RenderSystem/AnimatorNodes/LOTPolystarAnimator.m
//
//  LOTPolystarAnimator.m
//  Lottie
//
//  Created by brandon_withrow on 7/27/17.
//  Copyright © 2017 Airbnb. All rights reserved.
//

#import "LOTPolystarAnimator.h"
#import "LOTPointInterpolator.h"
#import "LOTNumberInterpolator.h"
#import "LOTBezierPath.h"
#import "CGGeometry+LOTAdditions.h"

const CGFloat kPOLYSTAR_MAGIC_NUMBER = .47829f;

@implementation LOTPolystarAnimator {
  LOTNumberInterpolator *_outerRadiusInterpolator;
  LOTNumberInterpolator *_innerRadiusInterpolator;
  LOTNumberInterpolator *_outerRoundnessInterpolator;
  LOTNumberInterpolator *_innerRoundnessInterpolator;
  LOTPointInterpolator *_positionInterpolator;
  LOTNumberInterpolator *_pointsInterpolator;
  LOTNumberInterpolator *_rotationInterpolator;
}

- (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
                             shapeStar:(LOTShapeStar *_Nonnull)shapeStar {
  self = [super initWithInputNode:inputNode keyName:shapeStar.keyname];
  if (self) {
    _outerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRadius.keyframes];
    _innerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.innerRadius.keyframes];
    _outerRoundnessInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRoundness.keyframes];
    _innerRoundnessInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.innerRoundness.keyframes];
    _pointsInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.numberOfPoints.keyframes];
    _rotationInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.rotation.keyframes];
    _positionInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeStar.position.keyframes];
  }
  return self;
}

- (NSDictionary *)valueInterpolators {
  return @{@"Points" : _pointsInterpolator,
           @"Position" : _positionInterpolator,
           @"Rotation" : _rotationInterpolator,
           @"Inner Radius" : _innerRadiusInterpolator,
           @"Outer Radius" : _outerRadiusInterpolator,
           @"Inner Roundness" : _innerRoundnessInterpolator,
           @"Outer Roundness" : _outerRoundnessInterpolator};
}

- (BOOL)needsUpdateForFrame:(NSNumber *)frame {
  return ([_outerRadiusInterpolator hasUpdateForFrame:frame] ||
          [_innerRadiusInterpolator hasUpdateForFrame:frame] ||
          [_outerRoundnessInterpolator hasUpdateForFrame:frame] ||
          [_innerRoundnessInterpolator hasUpdateForFrame:frame] ||
          [_pointsInterpolator hasUpdateForFrame:frame] ||
          [_rotationInterpolator hasUpdateForFrame:frame] ||
          [_positionInterpolator hasUpdateForFrame:frame]);
}

- (void)performLocalUpdate {
  CGFloat outerRadius = [_outerRadiusInterpolator floatValueForFrame:self.currentFrame];
  CGFloat innerRadius = [_innerRadiusInterpolator floatValueForFrame:self.currentFrame];
  CGFloat outerRoundness = [_outerRoundnessInterpolator floatValueForFrame:self.currentFrame] / 100.f;
  CGFloat innerRoundness = [_innerRoundnessInterpolator floatValueForFrame:self.currentFrame] / 100.f;
  CGFloat points = [_pointsInterpolator floatValueForFrame:self.currentFrame];
  CGFloat rotation = [_rotationInterpolator floatValueForFrame:self.currentFrame];
  CGPoint position = [_positionInterpolator pointValueForFrame:self.currentFrame];
  LOTBezierPath *path = [[LOTBezierPath alloc] init];
  path.cacheLengths = self.pathShouldCacheLengths;
  CGFloat currentAngle = LOT_DegreesToRadians(rotation - 90);
  CGFloat anglePerPoint = (CGFloat)((2 * M_PI) / points);
  CGFloat halfAnglePerPoint = anglePerPoint / 2.0f;
  CGFloat partialPointAmount = points - floor(points);
  if (partialPointAmount != 0) {
    currentAngle += halfAnglePerPoint * (1.f - partialPointAmount);
  }

  CGFloat x;
  CGFloat y;
  CGFloat previousX;
  CGFloat previousY;
  CGFloat partialPointRadius = 0;
  if (partialPointAmount != 0) {
    partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius);
    x = (CGFloat) (partialPointRadius * cosf(currentAngle));
    y = (CGFloat) (partialPointRadius * sinf(currentAngle));
    [path LOT_moveToPoint:CGPointMake(x, y)];
    currentAngle += anglePerPoint * partialPointAmount / 2.f;
  } else {
    x = (float) (outerRadius * cosf(currentAngle));
    y = (float) (outerRadius * sinf(currentAngle));
    [path LOT_moveToPoint:CGPointMake(x, y)];
    currentAngle += halfAnglePerPoint;
  }

  // True means the line will go to outer radius. False means inner radius.
  BOOL longSegment = false;
  CGFloat numPoints = ceil(points) * 2;
  for (int i = 0; i < numPoints; i++) {
    CGFloat radius = longSegment ? outerRadius : innerRadius;
    CGFloat dTheta = halfAnglePerPoint;
    if (partialPointRadius != 0 && i == numPoints - 2) {
      dTheta = anglePerPoint * partialPointAmount / 2.f;
    }
    if (partialPointRadius != 0 && i == numPoints - 1) {
      radius = partialPointRadius;
    }
    previousX = x;
    previousY = y;
    x = (CGFloat) (radius * cosf(currentAngle));
    y = (CGFloat) (radius * sinf(currentAngle));

    if (innerRoundness == 0 && outerRoundness == 0) {
      [path LOT_addLineToPoint:CGPointMake(x, y)];
    } else {
      CGFloat cp1Theta = (CGFloat) (atan2f(previousY, previousX) - M_PI / 2.f);
      CGFloat cp1Dx = (CGFloat) cosf(cp1Theta);
      CGFloat cp1Dy = (CGFloat) sinf(cp1Theta);
      
      CGFloat cp2Theta = (CGFloat) (atan2f(y, x) - M_PI / 2.f);
      CGFloat cp2Dx = (CGFloat) cosf(cp2Theta);
      CGFloat cp2Dy = (CGFloat) sinf(cp2Theta);
      
      CGFloat cp1Roundedness = longSegment ? innerRoundness : outerRoundness;
      CGFloat cp2Roundedness = longSegment ? outerRoundness : innerRoundness;
      CGFloat cp1Radius = longSegment ? innerRadius : outerRadius;
      CGFloat cp2Radius = longSegment ? outerRadius : innerRadius;
      
      CGFloat cp1x = cp1Radius * cp1Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp1Dx;
      CGFloat cp1y = cp1Radius * cp1Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp1Dy;
      CGFloat cp2x = cp2Radius * cp2Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp2Dx;
      CGFloat cp2y = cp2Radius * cp2Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp2Dy;
      if (partialPointAmount != 0) {
        if (i == 0) {
          cp1x *= partialPointAmount;
          cp1y *= partialPointAmount;
        } else if (i == numPoints - 1) {
          cp2x *= partialPointAmount;
          cp2y *= partialPointAmount;
        }
      }
      [path LOT_addCurveToPoint:CGPointMake(x, y)
                  controlPoint1:CGPointMake(previousX - cp1x, previousY - cp1y)
                  controlPoint2:CGPointMake(x + cp2x, y + cp2y)];
    }
    currentAngle += dTheta;
    longSegment = !longSegment;
  }
  [path LOT_closePath];
  [path LOT_applyTransform:CGAffineTransformMakeTranslation(position.x, position.y)];
  self.localPath = path;
}

@end
