[英]how to make a parallel bezier curve heuristically
I have only found this blog with a relevant answer http://seant23.wordpress.com/2010/11/12/offset-bezier-curves/ ,but unfortunately i don't know the language and can't understand the maths behind it. 我只在这个博客中找到了相关答案http://seant23.wordpress.com/2010/11/12/offset-bezier-curves/ ,但是很遗憾,我不懂该语言,也无法理解其背后的数学原理它。 What i need is to know how to make a bezier curve parallel to the one that i have. 我需要的是知道如何使贝塞尔曲线平行于我的贝塞尔曲线。
I have a Point, Segment and Path class, but i don't understand how to divide the path into segments. 我有一个Point,Segment和Path类,但是我不明白如何将路径分成多个段。 The Point class has the CGPoint location public variable, the Segment class has as properties 4 points, Point *control1, *control2, *point2 and*point1; Point类具有CGPoint位置公共变量,Segment类具有作为属性的4个点,即Point * control1,* control2,* point2和* point1; the Path class contains an NSMutableArray of segments and a Point startPoint. Path类包含一个NSMutableArray段和一个Point startPoint。
I am new to objective c and i would really appreciate some help, if not for my specific class construction, at least for a more general method. 我对目标c还是陌生的,如果不是针对我的特定类的构造,至少对于更通用的方法,我将不胜感激。
I don't know about the specific problem you're solving, but one cute (and very easy) solution is to just render the outline outline of a bezier curve, eg: 我不知道您要解决的特定问题,但是一种可爱(且非常简单)的解决方案是仅绘制贝塞尔曲线的轮廓,例如:
This is easily done using Core Graphics (in this case, a drawRect
of a UIView
subclass): 使用Core Graphics(在这种情况下,是UIView
子类的drawRect
)可以轻松完成此操作:
- (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;
}
Or in Swift 3: 或在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
}
If you really want to draw a parallel path, that's more complicated. 如果您真的想画一条平行的路径,那就更复杂了。 But you can render something like this (original bezier path in red, a "parallel" line in blue). 但是,您可以渲染类似的内容(红色的原始贝塞尔曲线路径,蓝色的“平行”线)。
I'm not entirely sure about the algorithm you've identified, but I rendered this by 我不确定您确定的算法,但是我通过
Thus, in Objective-C, that might look like: 因此,在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);
}
Or, in Swift 3: 或者,在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)
}
You might have seen the link to my article on Sean's blog, if not: http://pomax.github.io/bezierinfo/#offsetting covers offset curves in detail. 您可能会在Sean的博客上看到我的文章的链接,如果没有: http : //pomax.github.io/bezierinfo/#offsetting详细介绍了偏移曲线。 It refers to some subjects covered higher up in the article, like splitting curves at inflection points, but the takehome message is: 它指的是本文上半部分涉及的一些主题,例如在拐点处分割曲线,但要注意的是:
If there is no prebuilt offsetting function in your codebase, then you're going to have to implement it yourself, in which case you're going to have to take a day or two out of your dev and dedicate it to understanding how this works (for which I'd pretty much recommend running through the bezier article. writing the offsetting algorithm requires having the functions it depends on available). 如果您的代码库中没有预构建的偏移功能,那么您将必须自己实现它,在这种情况下,您将需要花一两天的时间来开发它,并专门研究它的工作原理(为此,我非常建议您遍历bezier文章。编写偏移算法需要具有依赖于可用功能的功能)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.