LOTPolystarAnimator.m 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. //
  2. // LOTPolystarAnimator.m
  3. // Lottie
  4. //
  5. // Created by brandon_withrow on 7/27/17.
  6. // Copyright © 2017 Airbnb. All rights reserved.
  7. //
  8. #import "LOTPolystarAnimator.h"
  9. #import "LOTPointInterpolator.h"
  10. #import "LOTNumberInterpolator.h"
  11. #import "LOTBezierPath.h"
  12. #import "CGGeometry+LOTAdditions.h"
  13. const CGFloat kPOLYSTAR_MAGIC_NUMBER = .47829f;
  14. @implementation LOTPolystarAnimator {
  15. LOTNumberInterpolator *_outerRadiusInterpolator;
  16. LOTNumberInterpolator *_innerRadiusInterpolator;
  17. LOTNumberInterpolator *_outerRoundnessInterpolator;
  18. LOTNumberInterpolator *_innerRoundnessInterpolator;
  19. LOTPointInterpolator *_positionInterpolator;
  20. LOTNumberInterpolator *_pointsInterpolator;
  21. LOTNumberInterpolator *_rotationInterpolator;
  22. }
  23. - (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
  24. shapeStar:(LOTShapeStar *_Nonnull)shapeStar {
  25. self = [super initWithInputNode:inputNode keyName:shapeStar.keyname];
  26. if (self) {
  27. _outerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRadius.keyframes];
  28. _innerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.innerRadius.keyframes];
  29. _outerRoundnessInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.outerRoundness.keyframes];
  30. _innerRoundnessInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.innerRoundness.keyframes];
  31. _pointsInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.numberOfPoints.keyframes];
  32. _rotationInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeStar.rotation.keyframes];
  33. _positionInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeStar.position.keyframes];
  34. }
  35. return self;
  36. }
  37. - (NSDictionary *)valueInterpolators {
  38. return @{@"Points" : _pointsInterpolator,
  39. @"Position" : _positionInterpolator,
  40. @"Rotation" : _rotationInterpolator,
  41. @"Inner Radius" : _innerRadiusInterpolator,
  42. @"Outer Radius" : _outerRadiusInterpolator,
  43. @"Inner Roundness" : _innerRoundnessInterpolator,
  44. @"Outer Roundness" : _outerRoundnessInterpolator};
  45. }
  46. - (BOOL)needsUpdateForFrame:(NSNumber *)frame {
  47. return ([_outerRadiusInterpolator hasUpdateForFrame:frame] ||
  48. [_innerRadiusInterpolator hasUpdateForFrame:frame] ||
  49. [_outerRoundnessInterpolator hasUpdateForFrame:frame] ||
  50. [_innerRoundnessInterpolator hasUpdateForFrame:frame] ||
  51. [_pointsInterpolator hasUpdateForFrame:frame] ||
  52. [_rotationInterpolator hasUpdateForFrame:frame] ||
  53. [_positionInterpolator hasUpdateForFrame:frame]);
  54. }
  55. - (void)performLocalUpdate {
  56. CGFloat outerRadius = [_outerRadiusInterpolator floatValueForFrame:self.currentFrame];
  57. CGFloat innerRadius = [_innerRadiusInterpolator floatValueForFrame:self.currentFrame];
  58. CGFloat outerRoundness = [_outerRoundnessInterpolator floatValueForFrame:self.currentFrame] / 100.f;
  59. CGFloat innerRoundness = [_innerRoundnessInterpolator floatValueForFrame:self.currentFrame] / 100.f;
  60. CGFloat points = [_pointsInterpolator floatValueForFrame:self.currentFrame];
  61. CGFloat rotation = [_rotationInterpolator floatValueForFrame:self.currentFrame];
  62. CGPoint position = [_positionInterpolator pointValueForFrame:self.currentFrame];
  63. LOTBezierPath *path = [[LOTBezierPath alloc] init];
  64. path.cacheLengths = self.pathShouldCacheLengths;
  65. CGFloat currentAngle = LOT_DegreesToRadians(rotation - 90);
  66. CGFloat anglePerPoint = (CGFloat)((2 * M_PI) / points);
  67. CGFloat halfAnglePerPoint = anglePerPoint / 2.0f;
  68. CGFloat partialPointAmount = points - floor(points);
  69. if (partialPointAmount != 0) {
  70. currentAngle += halfAnglePerPoint * (1.f - partialPointAmount);
  71. }
  72. CGFloat x;
  73. CGFloat y;
  74. CGFloat previousX;
  75. CGFloat previousY;
  76. CGFloat partialPointRadius = 0;
  77. if (partialPointAmount != 0) {
  78. partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius);
  79. x = (CGFloat) (partialPointRadius * cosf(currentAngle));
  80. y = (CGFloat) (partialPointRadius * sinf(currentAngle));
  81. [path LOT_moveToPoint:CGPointMake(x, y)];
  82. currentAngle += anglePerPoint * partialPointAmount / 2.f;
  83. } else {
  84. x = (float) (outerRadius * cosf(currentAngle));
  85. y = (float) (outerRadius * sinf(currentAngle));
  86. [path LOT_moveToPoint:CGPointMake(x, y)];
  87. currentAngle += halfAnglePerPoint;
  88. }
  89. // True means the line will go to outer radius. False means inner radius.
  90. BOOL longSegment = false;
  91. CGFloat numPoints = ceil(points) * 2;
  92. for (int i = 0; i < numPoints; i++) {
  93. CGFloat radius = longSegment ? outerRadius : innerRadius;
  94. CGFloat dTheta = halfAnglePerPoint;
  95. if (partialPointRadius != 0 && i == numPoints - 2) {
  96. dTheta = anglePerPoint * partialPointAmount / 2.f;
  97. }
  98. if (partialPointRadius != 0 && i == numPoints - 1) {
  99. radius = partialPointRadius;
  100. }
  101. previousX = x;
  102. previousY = y;
  103. x = (CGFloat) (radius * cosf(currentAngle));
  104. y = (CGFloat) (radius * sinf(currentAngle));
  105. if (innerRoundness == 0 && outerRoundness == 0) {
  106. [path LOT_addLineToPoint:CGPointMake(x, y)];
  107. } else {
  108. CGFloat cp1Theta = (CGFloat) (atan2f(previousY, previousX) - M_PI / 2.f);
  109. CGFloat cp1Dx = (CGFloat) cosf(cp1Theta);
  110. CGFloat cp1Dy = (CGFloat) sinf(cp1Theta);
  111. CGFloat cp2Theta = (CGFloat) (atan2f(y, x) - M_PI / 2.f);
  112. CGFloat cp2Dx = (CGFloat) cosf(cp2Theta);
  113. CGFloat cp2Dy = (CGFloat) sinf(cp2Theta);
  114. CGFloat cp1Roundedness = longSegment ? innerRoundness : outerRoundness;
  115. CGFloat cp2Roundedness = longSegment ? outerRoundness : innerRoundness;
  116. CGFloat cp1Radius = longSegment ? innerRadius : outerRadius;
  117. CGFloat cp2Radius = longSegment ? outerRadius : innerRadius;
  118. CGFloat cp1x = cp1Radius * cp1Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp1Dx;
  119. CGFloat cp1y = cp1Radius * cp1Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp1Dy;
  120. CGFloat cp2x = cp2Radius * cp2Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp2Dx;
  121. CGFloat cp2y = cp2Radius * cp2Roundedness * kPOLYSTAR_MAGIC_NUMBER * cp2Dy;
  122. if (partialPointAmount != 0) {
  123. if (i == 0) {
  124. cp1x *= partialPointAmount;
  125. cp1y *= partialPointAmount;
  126. } else if (i == numPoints - 1) {
  127. cp2x *= partialPointAmount;
  128. cp2y *= partialPointAmount;
  129. }
  130. }
  131. [path LOT_addCurveToPoint:CGPointMake(x, y)
  132. controlPoint1:CGPointMake(previousX - cp1x, previousY - cp1y)
  133. controlPoint2:CGPointMake(x + cp2x, y + cp2y)];
  134. }
  135. currentAngle += dTheta;
  136. longSegment = !longSegment;
  137. }
  138. [path LOT_closePath];
  139. [path LOT_applyTransform:CGAffineTransformMakeTranslation(position.x, position.y)];
  140. self.localPath = path;
  141. }
  142. @end