Drawing Smooth Curve

2020-08-01 06:00发布

问题:

I have two points left and right by adding some static value to Y and X is AVG of both left and right I have create 3rd point centre

After that I draw curve using bezier path between them And all works fine.

In above curve

Now I want to create two more points (5 Points curve).

One Between centre and left and one between centre and right .

I tried to take again Average and draw 5 Point curve using

  func drawFivePoint(_ startPoint: CGPoint?, leftCenterPoint: CGPoint?, toControlPoint controlPoint: CGPoint?, toRightControlPoint rightPoint: CGPoint?, toEnd endPoint: CGPoint?) {

    var arrPoints = [NSValue]()
    if startPoint != nil {
        arrPoints.append(NSValue(cgPoint: startPoint!))
    }

    if leftCenterPoint != nil && !(__CGPointEqualToPoint(leftCenterPoint!, .zero)) {
        arrPoints.append(NSValue(cgPoint: leftCenterPoint!))
    }

    if controlPoint != nil {
        arrPoints.append(NSValue(cgPoint: controlPoint!))
    }

    if rightPoint != nil && !(__CGPointEqualToPoint(rightPoint!, .zero)) {
        arrPoints.append(NSValue(cgPoint: rightPoint!))
    }

    if endPoint != nil {
        arrPoints.append(NSValue(cgPoint: endPoint!))
    }

    guard  let bezierPath = UIBezierPath.interpolateCGPoints(withHermite: arrPoints, closed: false) else {
        print("path is nil")

        return
    }
    curveSize = bezierPath.bounds
    let strokeColor = UIColor.white
    if curveLayer != nil {
        curveLayer?.removeFromSuperlayer()
        curveLayer = nil
    }
    curveLayer = CAShapeLayer()
    curveLayer?.lineWidth = 1.0 / self.zoomScale
    curveLayer?.fillColor = UIColor.clear.cgColor
    curveLayer?.path = bezierPath.cgPath
    curveLayer?.strokeColor = strokeColor.cgColor
    viewBase.layer.addSublayer(curveLayer!)
}

WRONG RESULT

Question : How To Calculate Points so shape is not affect and I get 5 Points on curve

回答1:

it is hard to say, what are you trying to achieve, but if your goal is to find points which are part of the Bezier curve which you defined in the first step, see how the Bezier curve is defined first. Wikipedia. Generally, only first and last control point of Bezier curve is also the part of the curve.

To find the point P(t) on the Bezier curve for a particular t (0..1) you can use De Casteljau's Algorithm

Try this simple snippet in your playground. I choose control points so, that x is linearly dependant on Bezier t parameter. The shape of y has the same shape as Bezier curve and is easy to see it as a graph of values in the playground

//: Playground - noun: a place where people can play

// point
struct Point {
    var x: Double
    var y: Double
}

// linear bezier
func linearBezier(p1: Point, p2: Point, t: Double)->Point {
    let px = p1.x + t*(p2.x - p1.x)
    let py = p1.y + t*(p2.y - p1.y)
    return Point(x: px, y: py)
}

// quadratic bezier
func quadraticBezier(p1: Point, p2: Point, p3: Point, t: Double)->Point {
    let p12 = linearBezier(p1: p1, p2: p2, t: t)
    let p23 = linearBezier(p1: p2, p2: p3, t: t)
    return linearBezier(p1: p12, p2: p23, t: t)
}

// cubic bezier
func cubicBezier(p1: Point, p2: Point, p3: Point, p4: Point, t: Double)->Point {
    let p12 = linearBezier(p1: p1, p2: p2, t: t)
    let p23 = linearBezier(p1: p2, p2: p3, t: t)
    let p34 = linearBezier(p1: p3, p2: p4, t: t)
    return quadraticBezier(p1: p12, p2: p23, p3: p34, t: t)
}

let p1 = Point(x: 0.0, y: 0.0)
let p2 = Point(x: 15.0, y: 10.0)
let p3 = Point(x: 30.0, y: 5.0)

for t in stride(from: 0.0, through: 1.0, by: 0.025) {
    let p = quadraticBezier(p1: p1, p2: p2, p3: p3, t: t)
    print(p.x, p.y)
    p.x
    p.y // see the values as a graph

}

let p4 = Point(x: 45.0, y: 10.0)

for t in stride(from: 0.0, through: 1.0, by: 0.025) {
    let p = cubicBezier(p1: p1, p2: p2, p3: p3, p4: p4, t: t)
    print(p.x, p.y)
    p.x
    p.y // see the values as a graph

}

One could further do it more generic

func bezier(controlPoints: [Point], t: Double)->[Point] {
    if controlPoints.count == 1 { return controlPoints }
    var reducedPoints: [Point] = []
    for i in 0..<(controlPoints.count - 1) {
        let p = linearBezier(p1: controlPoints[i], p2: controlPoints[i+1], t: t)
        reducedPoints.append(p)
    }
    return bezier(controlPoints: reducedPoints, t: t)
}

let points = [p1,p2,p3,p4]

for t in stride(from: 0.0, through: 1.0, by: 0.0125) {
    let p = bezier(controlPoints: points, t: t)
    p.count // it is alway 1 :-)
    p[0].x
    p[0].y
}

which gives you the same results. The function bezier could be used for 'any' number of control points. Take in the account, that the bezier function is valid only for t in the interval (0...1.0) by the definition, even though you can calculate the values in any interval.