我只发现了这个博客的相关答案http://seant23.wordpress.com/2010/11/12/offset-bezier-curves/,但遗憾的是我不懂语言,无法理解其背后的数学。我需要知道如何使贝塞尔曲线与我所拥有的曲线平行。
我有一个Point,Segment和Path类,但我不明白如何将路径分成段。 Point类具有CGPoint位置公共变量, Segment类有4个点属性,Point * control1,* control2,* point2和* point1; Path类包含一个NSMutableArray段和一个Point startPoint。
我是目标c的新手,我真的很感激一些帮助,如果不是我的特定班级建设,至少对于更通用的方法。
答案 0 :(得分:7)
我不知道您正在解决的具体问题,但一个可爱(而且非常简单)的解决方案就是渲染贝塞尔曲线的轮廓轮廓,例如:
使用Core Graphics(在这种情况下,是drawRect
子类的UIView
)可以轻松完成:
- (void)drawRect:(CGRect)rect {
CGPathRef path = [self newBezierPath];
CGPathRef outlinePath = CGPathCreateCopyByStrokingPath(path, NULL, 10, kCGLineCapButt, kCGLineJoinBevel, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 3.0);
CGContextAddPath(context, outlinePath);
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextDrawPath(context, kCGPathStroke);
CGPathRelease(path);
CGPathRelease(outlinePath);
}
- (CGPathRef)newBezierPath {
CGPoint point1 = CGPointMake(10.0, 50.0);
CGPoint point2 = CGPointMake(self.bounds.size.width - 10.0, point1.y + 150.0);
CGPoint controlPoint1 = CGPointMake(point1.x + 400.0, point1.y);
CGPoint controlPoint2 = CGPointMake(point2.x - 400.0, point2.y);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, point1.x, point1.y);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point2.x, point2.y);
return path;
}
或者在Swift 3中:
override func draw(_ rect: CGRect) {
let path = bezierPath().cgPath
let outlinePath = path.copy(strokingWithWidth: 10, lineCap: .butt, lineJoin: .bevel, miterLimit: 0)
let context = UIGraphicsGetCurrentContext()!
context.setLineWidth(3)
context.addPath(outlinePath)
context.setStrokeColor(UIColor.red.cgColor)
context.strokePath()
}
private func bezierPath() -> UIBezierPath {
let point1 = CGPoint(x: 10.0, y: 50.0)
let point2 = CGPoint(x: bounds.size.width - 10.0, y: point1.y + 150.0)
let controlPoint1 = CGPoint(x: point1.x + 400.0, y: point1.y)
let controlPoint2 = CGPoint(x: point2.x - 400.0, y: point2.y)
let path = UIBezierPath()
path.move(to: point1)
path.addCurve(to: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
return path
}
如果你真的想画一条平行路径,那就更复杂了。但你可以渲染这样的东西(红色的原始bezier路径,"平行"蓝色线)。
我对您已识别的算法并不完全确定,但我通过
进行了渲染因此,在Objective-C中,可能看起来像:
- (void)drawRect:(CGRect)rect {
CGPoint point1 = CGPointMake(10.0, 50.0);
CGPoint point2 = CGPointMake(self.bounds.size.width - 10.0, point1.y + 150.0);
CGPoint controlPoint1 = CGPointMake(point1.x + 400.0, point1.y);
CGPoint controlPoint2 = CGPointMake(point2.x - 400.0, point2.y);
// draw original bezier path in red
[[UIColor redColor] setStroke];
[[self bezierPathFromPoint1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2] stroke];
// calculate and draw offset bezier curve in blue
[[UIColor blueColor] setStroke];
[[self offsetBezierPathBy:10.0
point1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2] stroke];
}
- (UIBezierPath *)bezierPathFromPoint1:(CGPoint)point1
point2:(CGPoint)point2
controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2 {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point1];
[path addCurveToPoint:point2 controlPoint1:controlPoint1 controlPoint2:controlPoint2];
return path;
}
- (UIBezierPath *)offsetBezierPathBy:(CGFloat)offset
point1:(CGPoint)point1
point2:(CGPoint)point2
controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2 {
UIBezierPath *path = [UIBezierPath bezierPath];
static NSInteger numberOfPoints = 100;
CGPoint previousPoint = [self cubicBezierAtTime:0.0
point1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2];
CGPoint point;
double angle;
for (NSInteger i = 1; i <= numberOfPoints; i++) {
double t = (double) i / numberOfPoints;
point = [self cubicBezierAtTime:t
point1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2];
// calculate the angle to the offset point
// this is the angle between the two points, plus 90 degrees (pi / 2.0)
angle = atan2(point.y - previousPoint.y, point.x - previousPoint.x) + M_PI_2;
if (i == 1)
[path moveToPoint:[self offsetPoint:previousPoint by:offset angle:angle]];
previousPoint = point;
[path addLineToPoint:[self offsetPoint:previousPoint by:offset angle:angle]];
}
return path;
}
// return point offset by particular distance and particular angle
- (CGPoint)offsetPoint:(CGPoint)point by:(CGFloat)offset angle:(double)angle {
return CGPointMake(point.x + cos(angle) * offset, point.y + sin(angle) * offset);
}
// Manually calculate cubic bezier curve
//
// B(t) = (1-t)^3 * point1 + 3 * (1-t)^2 * t controlPoint1 + 3 * (1-t) * t^2 * pointPoint2 + t^3 * point2
- (CGPoint)cubicBezierAtTime:(double)t
point1:(CGPoint)point1
point2:(CGPoint)point2
controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2 {
double oneMinusT = 1.0 - t;
double oneMinusTSquared = oneMinusT * oneMinusT;
double oneMinusTCubed = oneMinusTSquared * oneMinusT;
double tSquared = t * t;
double tCubed = tSquared * t;
CGFloat x = point1.x * oneMinusTCubed +
3.0 * oneMinusTSquared * t * controlPoint1.x +
3.0 * oneMinusT * tSquared * controlPoint2.x +
tCubed * point2.x;
CGFloat y = point1.y * oneMinusTCubed +
3.0 * oneMinusTSquared * t * controlPoint1.y +
3.0 * oneMinusT * tSquared * controlPoint2.y +
tCubed * point2.y;
return CGPointMake(x, y);
}
或者,在Swift 3中:
override func draw(_ rect: CGRect) {
let point1 = CGPoint(x: 10.0, y: 50.0)
let point2 = CGPoint(x: bounds.size.width - 10.0, y: point1.y + 150.0)
let controlPoint1 = CGPoint(x: point1.x + 400.0, y: point1.y)
let controlPoint2 = CGPoint(x: point2.x - 400.0, y: point2.y)
UIColor.red.setStroke()
bezierPath(from: point1, to: point2, withControl: controlPoint1, and: controlPoint2).stroke()
UIColor.blue.setStroke()
offSetBezierPath(by: 5, from: point1, to: point2, withControl: controlPoint1, and: controlPoint2).stroke()
}
private func bezierPath(from point1: CGPoint, to point2: CGPoint, withControl controlPoint1: CGPoint, and controlPoint2:CGPoint) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: point1)
path.addCurve(to: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
return path
}
private func offSetBezierPath(by offset: CGFloat, from point1: CGPoint, to point2: CGPoint, withControl controlPoint1: CGPoint, and controlPoint2:CGPoint) -> UIBezierPath {
let path = UIBezierPath()
let numberOfPoints = 100
var previousPoint = cubicBezier(at: 0, point1: point1, point2: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
for i in 1 ... numberOfPoints {
let time = CGFloat(i) / CGFloat(numberOfPoints)
let point = cubicBezier(at: time, point1: point1, point2: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
// calculate the angle to the offset point
// this is the angle between the two points, plus 90 degrees (pi / 2.0)
let angle = atan2(point.y - previousPoint.y, point.x - previousPoint.x) + .pi / 2;
if i == 1 {
path.move(to: calculateOffset(of: previousPoint, by: offset, angle: angle))
}
previousPoint = point
path.addLine(to: calculateOffset(of: previousPoint, by: offset, angle: angle))
}
return path
}
/// Return point offset by particular distance and particular angle
///
/// - Parameters:
/// - point: Point to offset.
/// - offset: How much to offset by.
/// - angle: At what angle.
///
/// - Returns: New `CGPoint`.
private func calculateOffset(of point: CGPoint, by offset: CGFloat, angle: CGFloat) -> CGPoint {
return CGPoint(x: point.x + cos(angle) * offset, y: point.y + sin(angle) * offset)
}
/// Manually calculate cubic bezier curve
///
/// B(t) = (1-t)^3 * point1 + 3 * (1-t)^2 * t controlPoint1 + 3 * (1-t) * t^2 * pointPoint2 + t^3 * point2
///
/// - Parameters:
/// - time: Time, a value between zero and one.
/// - point1: Starting point.
/// - point2: Ending point.
/// - controlPoint1: First control point.
/// - controlPoint2: Second control point.
///
/// - Returns: Point on bezier curve.
private func cubicBezier(at time: CGFloat, point1: CGPoint, point2: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint) -> CGPoint {
let oneMinusT = 1.0 - time
let oneMinusTSquared = oneMinusT * oneMinusT
let oneMinusTCubed = oneMinusTSquared * oneMinusT
let tSquared = time * time
let tCubed = tSquared * time
var x = point1.x * oneMinusTCubed
x += 3.0 * oneMinusTSquared * time * controlPoint1.x
x += 3.0 * oneMinusT * tSquared * controlPoint2.x
x += tCubed * point2.x
var y = point1.y * oneMinusTCubed
y += 3.0 * oneMinusTSquared * time * controlPoint1.y
y += 3.0 * oneMinusT * tSquared * controlPoint2.y
y += tCubed * point2.y
return CGPoint(x: x, y: y)
}
答案 1 :(得分:3)
您可能已经看过我在Sean博客上发表文章的链接,如果没有:http://pomax.github.io/bezierinfo/#offsetting详细介绍偏移曲线。它指的是文章中较高的一些主题,如在拐点处分割曲线,但是主题信息是:
如果您的代码库中没有预先构建的抵消功能,那么您将不得不自己实施,在这种情况下,您将不得不花费一两天的时间来开发并将其用于理解这是如何工作的(我非常建议你继续阅读bezier文章。编写偏移算法需要具备可用的功能)。