如何启发式制作平行贝塞尔曲线

SummerCode

我只在这个博客中找到了相关答案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还是陌生的,如果不是针对我的特定类的构造,至少对于更通用的方法,我将不胜感激。

我不知道您要解决的特定问题,但是一个可爱(且非常简单)的解决方案是仅绘制贝塞尔曲线的轮廓,例如:

贝塞尔曲线的轮廓

使用Core Graphics(在本例drawRect中为UIView子类的a 可以轻松完成此操作

- (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
}

如果您真的想画一条平行的路径,那就更复杂了。但是,您可以渲染类似的内容(红色的原始贝塞尔曲线路径,蓝色的“平行”线)。

在此处输入图片说明

我不确定您确定的算法,但是我通过

  • 自己计算贝塞尔曲线点(红色路径),将其细化到足以使效果平滑的程度;
  • 计算每个点和下一个点之间的角度;
  • 通过获取贝塞尔曲线路径上的点,并计算与刚确定的角度I垂直的新点,来计算偏移路径(蓝色坐标)的坐标;
  • 使用这些偏移点坐标,绘制一系列新的线段,以将平行线渲染到贝塞尔曲线。

因此,在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)
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何生成“厚”贝塞尔曲线?

来自分类Dev

如何使精灵遵循贝塞尔曲线

来自分类Dev

如何从图像模拟贝塞尔曲线?

来自分类Dev

贝塞尔曲线数学

来自分类Dev

如何定义水壶的启发式功能?

来自分类Dev

曲线(贝塞尔曲线)渐变

来自分类Dev

如何编码n阶贝塞尔曲线

来自分类Dev

如何平滑三次贝塞尔曲线过渡

来自分类Dev

如何在Android中绘制贝塞尔曲线

来自分类Dev

如何找到贝塞尔曲线的控制点

来自分类Dev

如何在贝塞尔曲线中添加渐变?

来自分类Dev

如何设置贝塞尔曲线的控制点以折线?

来自分类Dev

贝塞尔曲线与指定椭圆相交时如何检测?

来自分类Dev

如何使用AS3设置贝塞尔曲线的动画?

来自分类Dev

如何找到贝塞尔曲线的控制点

来自分类Dev

如何使贝塞尔曲线中的线接点平滑?

来自分类Dev

如何在UIView中绘制贝塞尔曲线

来自分类Dev

如何绘制贝塞尔曲线 C#

来自分类Dev

通过示例了解如何绘制 SVG 贝塞尔曲线

来自分类Dev

如何将二次贝塞尔曲线代码转换为三次贝塞尔曲线?

来自分类Dev

用贝塞尔曲线制作曲线的动画填充颜色?

来自分类Dev

绘制SVG贝塞尔曲线

来自分类Dev

贝塞尔曲线的均匀离散

来自分类Dev

绘制贝塞尔曲线的半圆弧

来自分类Dev

在Android的贝塞尔曲线外换行

来自分类Dev

使用Firemonkey绘制贝塞尔曲线

来自分类Dev

用Java绘制贝塞尔曲线

来自分类Dev

简化高阶贝塞尔曲线

来自分类Dev

CSS中的贝塞尔曲线?