在第一个示例中,您从具有多个闭合子路径的路径开始。显然,您想要扭曲子路径的中心,但保持各个子路径相对于其(新)中心不扭曲。我将忽略这一点,因为即使没有它,解决方案也已经非常复杂。
那么,让我们考虑一下如何定义“扭曲场”。我们将使用三个控制点:
扭曲使固定点保持不变。它将起点移动到终点rotation and scaling, not通过简单地插值坐标。
此外,它还根据距固定点的距离应用旋转和缩放。而且不仅仅是基于简单的欧几里得距离。请注意,我们不想对图片中“V”形状的顶部端点应用任何旋转或缩放,即使这些端点距固定点有可测量的欧几里德距离。我们想要测量沿fixedPoint->startPoint向量的距离,并随着距离的增加应用更多的旋转/缩放。
这一切都需要一些相当重的三角学。我不会尝试解释细节。我只是要把代码转储给你,作为一个类别UIBezierPath
:
UIBezierPath+Rob_warp.h
#import <UIKit/UIKit.h>
@interface UIBezierPath (Rob_warp)
- (UIBezierPath *)Rob_warpedWithFixedPoint:(CGPoint)fixedPoint startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
@end
UIBezierPath+Rob_warp.m
请注意,您将需要Rob_forEach
类别来自这个答案.
#import "UIBezierPath+Rob_warp.h"
#import "UIBezierPath+Rob_forEach.h"
#import <tgmath.h>
static CGPoint minus(CGPoint a, CGPoint b) {
return CGPointMake(a.x - b.x, a.y - b.y);
}
static CGFloat length(CGPoint vector) {
return hypot(vector.x, vector.y);
}
static CGFloat dotProduct(CGPoint a, CGPoint b) {
return a.x * b.x + a.y * b.y;
}
static CGFloat crossProductMagnitude(CGPoint a, CGPoint b) {
return a.x * b.y - a.y * b.x;
}
@implementation UIBezierPath (Rob_warp)
- (UIBezierPath *)Rob_warpedWithFixedPoint:(CGPoint)fixedPoint startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGPoint startVector = minus(startPoint, fixedPoint);
CGFloat startLength = length(startVector);
CGPoint endVector = minus(endPoint, fixedPoint);
CGFloat endLength = length(minus(endPoint, fixedPoint));
CGFloat scale = endLength / startLength;
CGFloat dx = dotProduct(startVector, endVector);
CGFloat dy = crossProductMagnitude(startVector, endVector);
CGFloat radians = atan2(dy, dx);
CGPoint (^warp)(CGPoint) = ^(CGPoint input){
CGAffineTransform t = CGAffineTransformMakeTranslation(-fixedPoint.x, -fixedPoint.y);
CGPoint inputVector = minus(input, fixedPoint);
CGFloat factor = dotProduct(inputVector, startVector) / (startLength * startLength);
CGAffineTransform w = CGAffineTransformMakeRotation(radians * factor);
t = CGAffineTransformConcat(t, w);
CGFloat factoredScale = pow(scale, factor);
t = CGAffineTransformConcat(t, CGAffineTransformMakeScale(factoredScale, factoredScale));
// Note: next line is not the same as CGAffineTransformTranslate!
t = CGAffineTransformConcat(t, CGAffineTransformMakeTranslation(fixedPoint.x, fixedPoint.y));
return CGPointApplyAffineTransform(input, t);
};
UIBezierPath *copy = [self.class bezierPath];
[self Rob_forEachMove:^(CGPoint destination) {
[copy moveToPoint:warp(destination)];
} line:^(CGPoint destination) {
[copy addLineToPoint:warp(destination)];
} quad:^(CGPoint control, CGPoint destination) {
[copy addQuadCurveToPoint:warp(destination) controlPoint:warp(control)];
} cubic:^(CGPoint control0, CGPoint control1, CGPoint destination) {
[copy addCurveToPoint:warp(destination) controlPoint1:warp(control0) controlPoint2:warp(control1)];
} close:^{
[copy closePath];
}];
return copy;
}
@end
好吧,那么你如何使用这个疯狂的东西呢?对于示例中类似“V”的路径,您可以这样做:
CGRect rect = path.bounds;
CGPoint fixedPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint startPoint = CGPointMake(fixedPoint.x, CGRectGetMaxY(rect));
path = [path Rob_warpedWithFixedPoint:fixedPoint startPoint:startPoint endPoint:endAnchor];
我将fixedPoint 计算为路径边界框上边缘的中心,将startPoint 计算为下边缘的中心。这endAnchor
在我的测试程序中处于用户控制之下。在模拟器中看起来像这样:
气泡型路径如下所示:
您可以在这里找到我的测试项目:https://github.com/mayoff/path-warp