2013-08-02 4 views
1

이 질문에 대한 답변이 http://seant23.wordpress.com/2010/11/12/offset-bezier-curves/ 인이 블로그를 찾았지만 불행히도 언어를 모르며 그 뒤에있는 수학을 이해할 수 없습니다. 내가 필요한 것은 베지에 곡선을 평행하게 만드는 법을 아는 것입니다.병렬 베 지어 곡선을 경험적으로 만드는 방법

포인트, 세그먼트 및 경로 클래스가 있지만 경로로 세그먼트를 나누는 방법을 이해하지 못합니다. Point 클래스의 CGPoint 위치 공용 변수는 입니다. Segment 클래스의 속성은 4 포인트, Point * control1, * control2, * point2 및 * point1입니다. Path 클래스에는 세그먼트의 NSMutableArray와 Point startPoint가 포함됩니다.

나는 객관적인 C를 처음 접했고, 나의 구체적인 클래스 구성이 아니라면, 좀 더 일반적인 방법으로는 도움이된다.

outline of bezier curve

:

+0

나는 베지에 곡선을 그릴 필요가있다. 내가 이미 – SummerCode

답변

6
난 당신이 해결하고있는 특정 문제에 대해 알고하지 않습니다

하지만, 하나의 귀여운 (아주 쉬운) 솔루션은 단지 베 지어 곡선의 윤곽 개요, 예를 렌더링하는 것입니다 이것은 쉽게 코어 그래픽을 사용하여 수행됩니다 (이 경우에 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; 
} 

또는 스위프트 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 
} 

병렬 경로를 실제로 그려 보려면 더 복잡합니다. 그러나 당신은 이와 같은 것을 렌더링 할 수 있습니다 (원래의 베 지어 경로는 빨간색으로, "평행선"은 파란색으로).

enter image description here

난 당신이 식별 한 알고리즘에 대해 완전히 모르겠지만, 미세 충분히 그것을 깨고 자신을 베 지어 포인트 (빨간 경로)를 계산

  • 하여 렌더링 효과를 부드럽게;
  • 각 점과 다음 점 사이의 각도를 계산합니다.
  • 베 지어 경로의 점을 찍고 방금 결정한 각도에 수직 인 새 점을 계산하여 오프셋 경로의 좌표 (파란 경로)를 계산합니다. 이러한 오프셋 포인트 좌표를 사용하여
  • 을 사용하면 평행선을 베 지어로 렌더링하는 새로운 일련의 선분을 그립니다. 스위프트 3,

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

    또는 :

따라서, 목표 - C에서, 그처럼 보일 수 있습니다

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) 
} 
당신은 숀의 블로그에 나의 기사에 대한 링크를 볼 수도
+0

을 가지고있다. 나는 이미 클래스의 내 자신의 구현으로 베 지어 커브를 그릴 수 있었지만, 어떻게 병렬 처리를하는지 알 수 없다. 내가 그것으로부터 주어진 거리에있는 사람. – SummerCode

+0

이것은 대단합니다! 이 대답을 신속하게 2.2-3/ios 9에 맞게 업데이트 할 수 있습니까? –

+0

스위프트 3 변환을 추가했습니다. Swift 2.3 변환은 꽤 유사하며 매우 자명합니다. – Rob

3

, 그렇지 않은 경우 : http://pomax.github.io/bezierinfo/#offsetting은 오프셋 커브를 자세히 설명합니다. 이 변곡점에서 분할 곡선처럼 문서에서 높은 은폐 일부 피사체를 의미하지만, takehome 메시지는 다음과 같습니다

    당신은 임의의 곡선 B에 대한 진정한 오프셋 곡선 C를 만들 수 없습니다
  1. 이 기사에서는 이유를 설명하지만 tl, dr은 베 지어 곡선이 정수 다항식이며 분 에지 경우를 제외하고는 오프셋 곡선이 수학 함수로 완벽하게 모델링 될 수 있지만 정수 다항식이 아니며 그런 것은 다른 베 지어 커브가 아니기 때문에 편리합니다.
  2. "곡선처럼 보입니다"다각형으로 곡선을 그리며 전개 할 수 있습니다. 이 작업은 정말 빨라질 수 있습니다. 충분한 세그먼트를 사용하면 잘 보일 것입니다. 크기가 조정되지 않으며 오프셋 거리에 따라 사용할 세그먼트 수를 결정해야합니다.
  3. 의 작은 섹션으로 커브를자를 수있어 다른 커브로 인해 정밀도가 손실되지 않습니다. 이것은 느리지 만 커브와 오프셋 사이의 임의의 거리를 허용합니다.

코드베이스의 기능을 상쇄에는 미리 만들어진이, 당신이이 경우에 당신이 당신의 디바이스에서 하루 이틀을 가지고 헌신해야 할거야, 스스로를 구현해야 할 것 없다하고있는 경우 그것은 어떻게 작동하는지 이해하는 것입니다 (베 지어 (bezier) 기사를 통해 실행하는 것이 좋습니다) 오프셋 알고리즘을 작성하려면 사용 가능한 함수가 있어야합니다.