Ответ 1
Изменить
FWIW: Этот ответ служит его образовательной цели, объясняя, что CGPathAddArcToPoint(...)
делает для вас. Я бы настоятельно рекомендовал вам прочитать его, поскольку он поможет вам понять и оценить API CGPath. Затем вы должны использовать это, как показано в an0 answer, вместо этого кода, когда вы округлите края в своем приложении. Этот код следует использовать только в качестве ссылки, если вы хотите поиграть и узнать о геометрических расчетах, подобных этому.
Оригинальный ответ
Потому что я так быстро нахожу такие вопросы, я должен был ответить на него:)
Это длинный ответ. Короткий вариант: D
Примечание. Для моей собственной простоты мое решение делает некоторые предположения о точках, которые используются для формирования треугольника, например:
- Площадь треугольника достаточно велика, чтобы соответствовать закругленному углу (например, высота треугольника больше диаметра кругов в углах. Я не проверяю или не пытаюсь предотвратить какие-либо странные результаты это может произойти иначе.
- Углы указаны в часовом порядке. Вы могли бы заставить его работать для любого заказа, но для простоты было достаточно справедливого ограничения.
Если вы хотите, вы можете использовать тот же метод для округления любого полигона до тех пор, пока он строго выпуклый (т.е. не заостренная звезда). Я не буду объяснять, как это сделать, но он следует тому же принципу.
Все начинается с треугольника, который вы хотите обогнуть по углам с некоторым радиусом, r:
Квадратный треугольник должен содержаться в треугольнике с тонкими точками, поэтому первый шаг - найти места как можно ближе к углам, где вы можете поместить круг с радиусом, r.
Простой способ сделать это - создать 3 новые линии, параллельные трем сторонам треугольника, и сдвинуть каждое из расстояния r внутрь, ортогональное стороне оригинальной стороны.
Для этого вы вычисляете наклон/угол каждой линии и смещение для применения к двум новым точкам:
CGFloat angle = atan2f(end.y - start.y,
end.x - start.x);
CGVector offset = CGVectorMake(-sinf(angle)*radius,
cosf(angle)*radius);
Примечание. Для ясности я использую тип CGVector (доступный в iOS 7), но вы можете просто использовать точку или размер для работы с предыдущими версиями ОС.
то вы добавляете смещение как для начальной, так и для конечной точки для каждой строки:
CGPoint offsetStart = CGPointMake(start.x + offset.dx,
start.y + offset.dy);
CGPoint offsetEnd = CGPointMake(end.x + offset.dx,
end.y + offset.dy);
Когда вы это сделаете, вы увидите, что три линии пересекаются друг с другом в трех местах:
Каждая точка пересечения - это точно расстояние r от двух сторон (при условии, что треугольник достаточно велик, как указано выше).
Вы можете рассчитать пересечение двух строк как:
// (x1⋅y2-y1⋅x2)(x3-x4) - (x1-x2)(x3⋅y4-y3⋅x4)
// px = –––––––––––––––––––––––––––––––––––––––––––
// (x1-x2)(y3-y4) - (y1-y2)(x3-x4)
// (x1⋅y2-y1⋅x2)(y3-y4) - (y1-y2)(x3⋅y4-y3⋅x4)
// py = –––––––––––––––––––––––––––––––––––––––––––
// (x1-x2)(y3-y4) - (y1-y2)(x3-x4)
CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
CGPoint intersection = CGPointMake(intersectionX, intersectionY);
где (x1, y1) - (x2, y2) - первая строка, а (x3, y3) - (x4, y4) - вторая строка.
Если вы затем поместите круг с радиусом r в каждую точку пересечения, вы увидите, что он действительно будет для закругленного треугольника (игнорируя различные ширины линий треугольника и кругов):
Теперь, чтобы создать округленный треугольник, вы хотите создать путь, который изменяется от линии до дуги до строки (и т.д.) в точках, где исходный треугольник ортогонален точкам пересечения. Это также точка, в которой круги касаются исходного треугольника.
Зная склоны всех трех сторон в треугольнике, радиус угла и центр кругов (точки пересечения), угол начала и останова для каждого закругленного угла - это угол наклона этой стороны - 90 градусов. Чтобы сгруппировать эти вещи вместе, я создал структуру в своем коде, но вам не нужно, если вы не хотите:
typedef struct {
CGPoint centerPoint;
CGFloat startAngle;
CGFloat endAngle;
} CornerPoint;
Чтобы уменьшить дублирование кода, я создал для себя метод, который вычисляет пересечение и углы для одной точки, задавая линию от одной точки, через другую, до конечной точки (она не закрыта, чтобы она не была треугольником):
Код выглядит следующим образом (это действительно просто код, который я показал выше, вместе):
- (CornerPoint)roundedCornerWithLinesFrom:(CGPoint)from
via:(CGPoint)via
to:(CGPoint)to
withRadius:(CGFloat)radius
{
CGFloat fromAngle = atan2f(via.y - from.y,
via.x - from.x);
CGFloat toAngle = atan2f(to.y - via.y,
to.x - via.x);
CGVector fromOffset = CGVectorMake(-sinf(fromAngle)*radius,
cosf(fromAngle)*radius);
CGVector toOffset = CGVectorMake(-sinf(toAngle)*radius,
cosf(toAngle)*radius);
CGFloat x1 = from.x +fromOffset.dx;
CGFloat y1 = from.y +fromOffset.dy;
CGFloat x2 = via.x +fromOffset.dx;
CGFloat y2 = via.y +fromOffset.dy;
CGFloat x3 = via.x +toOffset.dx;
CGFloat y3 = via.y +toOffset.dy;
CGFloat x4 = to.x +toOffset.dx;
CGFloat y4 = to.y +toOffset.dy;
CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
CGPoint intersection = CGPointMake(intersectionX, intersectionY);
CornerPoint corner;
corner.centerPoint = intersection;
corner.startAngle = fromAngle - M_PI_2;
corner.endAngle = toAngle - M_PI_2;
return corner;
}
Затем я использовал этот код 3 раза для вычисления трех углов:
CornerPoint leftCorner = [self roundedCornerWithLinesFrom:right
via:left
to:top
withRadius:radius];
CornerPoint topCorner = [self roundedCornerWithLinesFrom:left
via:top
to:right
withRadius:radius];
CornerPoint rightCorner = [self roundedCornerWithLinesFrom:top
via:right
to:left
withRadius:radius];
Теперь, имея все необходимые данные, начинается часть, в которой мы создаем фактический путь. Я буду полагаться на то, что CGPathAddArc добавит прямую линию от текущей точки к начальной точке, чтобы не рисовать эти строки (это документированное поведение).
Единственной точкой, которую я должен вычислить вручную, является начальная точка пути. Я выбираю начало нижнего правого угла (нет конкретной причины). Оттуда вы просто добавляете дугу с центром в точках пересечения от начального и конечного углов:
CGMutablePathRef roundedTrianglePath = CGPathCreateMutable();
// manually calculated start point
CGPathMoveToPoint(roundedTrianglePath, NULL,
leftCorner.centerPoint.x + radius*cosf(leftCorner.startAngle),
leftCorner.centerPoint.y + radius*sinf(leftCorner.startAngle));
// add 3 arcs in the 3 corners
CGPathAddArc(roundedTrianglePath, NULL,
leftCorner.centerPoint.x, leftCorner.centerPoint.y,
radius,
leftCorner.startAngle, leftCorner.endAngle,
NO);
CGPathAddArc(roundedTrianglePath, NULL,
topCorner.centerPoint.x, topCorner.centerPoint.y,
radius,
topCorner.startAngle, topCorner.endAngle,
NO);
CGPathAddArc(roundedTrianglePath, NULL,
rightCorner.centerPoint.x, rightCorner.centerPoint.y,
radius,
rightCorner.startAngle, rightCorner.endAngle,
NO);
// close the path
CGPathCloseSubpath(roundedTrianglePath);
Посмотрите что-то вроде этого:
Конечный результат без всех строк поддержки выглядит следующим образом: