Как кривить CGMutablePath?

Со следующей формой:

enter image description here

Мне было интересно, как вы можете это сделать так:

enter image description here

Аналогично:

enter image description here

Я предполагаю, что все круги/строки упакованы в один CGMutablePath, а затем к ней применяется какая-то кривая, дуга или квад-кривая, хотя у меня возникают проблемы с приближением для его тиражирования. Кто-нибудь знает, как это сделать?

Ответ 1

В первом примере вы начинаете с пути, который имеет несколько закрытых подпапок. По-видимому, вы хотите деформировать центры подпапок, но оставляйте отдельные подпуты необоснованными относительно своих (новых) центров. Я проигнорирую это, потому что решение даже без этого уже ужасно сложно.

Итак, рассмотрим, как определить "поле warp". Мы будем использовать три контрольные точки:

example with control points

Warp оставляет неизменным фиксированный номер. Он перемещает startPoint в endPoint с помощью вращения и масштабирования, не, просто интерполируя координаты.

Кроме того, он применяет поворот и масштабирование на основе расстояния от fixedPoint. И не только на основе простого евклидова расстояния. Обратите внимание, что мы не хотим применять какое-либо вращение или масштабирование к верхним конечным точкам формы "V" на изображении, хотя эти конечные точки являются измеримым евклидовым расстоянием от fixedPoint. Мы хотим измерить расстояние вдоль вектора 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 находится под управлением пользователя в моей тестовой программе. Это похоже на симулятор:

demo with V

Путь типа пузырька выглядит следующим образом:

demo with bubbles

Здесь вы можете найти мой тестовый проект: https://github.com/mayoff/path-warp