2016-08-02 37 views
2

我正試圖用UIBezierPath繪製一個簡單的拋物線形狀。我有一個maxPoint和一個boundingRect其中我基於拋物線的寬度和拉伸。
這是我做得出拋物線函數(我畫在容器視圖中的拋物線,rectcontainer.bounds):不可靠的UIBezierPath曲線機制,controlPoint和曲線點

func addParabolaWithMax(maxPoint: CGPoint, inRect boundingRect: CGRect) { 
    let path = UIBezierPath() 

    let p1 = CGPointMake(1, CGRectGetMaxY(boundingRect)-1) 
    let p3 = CGPointMake(CGRectGetMaxX(boundingRect)-1, CGRectGetMaxY(boundingRect)-1) 

    path.moveToPoint(p1) 
    path.addQuadCurveToPoint(p3, controlPoint: maxPoint) 

    // Drawing code 
    ... 
} 

我的問題是,我想我在函數發送到maxPoint成爲拋物線本身的實際極端點。例如,如果我發送(CGRectGetMidX(container.bounds), 0),最高點應該位於最頂端的中心。但是,在使用此功能與這個特殊的點,這是個什麼結果如下:

enter image description here

那麼究竟路徑在這裏所做的?或換句話說,我怎樣才能從controlPoint到我需要的實際最大點?我試着根據boundingRect的高度加上和減去y值的不同值,但我找不到合適的組合,因爲在具有不同y值的不同點中,它的行爲有所不同。似乎有某種倍增器被添加進來,我該如何解決它?

回答

4

5月應用adam.wulf的解決方案是好的,但它實際上並沒有創造一個拋物線。要創建拋物線,我們需要計算給定二次曲線中點的控制點。貝塞爾路徑只是數學;我們可以很容易地計算出來。我們只需要反演Bézier函數並在t = 0.5時求解它。

在0.5(中點)的Bézier解法在Draw a quadratic Bézier curve through three given points處得到很好的結果。

2*Pc - P0/2 - P2/2 

哪裏Pc是我們想通過和P0P2是結束點的點。 (計算Bézier在其他點不是很直觀,t = 0.25的值不是「沿路徑的四分之一」。但幸運的是,對於我們的目的,t = 0.5與我們的直覺很好地匹配的「中點」)。

鑑於我們的解決方案,我們可以編寫我們的代碼。原諒翻譯Swift 3;我的Xcode 7.3版本對iOS遊戲機並不滿意,但應該很容易轉換爲2.2。

func addParabolaWithMax(maxPoint: CGPoint, inRect boundingRect: CGRect) -> UIBezierPath { 

    func halfPoint1D(p0: CGFloat, p2: CGFloat, control: CGFloat) -> CGFloat { 
     return 2 * control - p0/2 - p2/2 
    } 

    let path = UIBezierPath() 

    let p0 = CGPoint(x: 0, y: boundingRect.maxY) 
    let p2 = CGPoint(x: boundingRect.maxX, y: boundingRect.maxY) 

    let p1 = CGPoint(x: halfPoint1D(p0: p0.x, p2: p2.x, control: maxPoint.x), 
        y: halfPoint1D(p0: p0.y, p2: p2.y, control: maxPoint.y)) 

    path.move(to: p0) 
    path.addQuadCurve(to: p2, controlPoint: p1) 
    return path 
} 

halfPoint1D函數是我們解決方案的一維實現。對於我們的二維CGPoint,我們只需要調用它兩次。

如果我只能推薦一種理解Bézier曲線的資源,它可能是維基百科的"Constructing Bézier curves"部分。研究顯示曲線如何產生的小動畫我覺得非常有啓發性。 「具體案例」部分也很有用。爲了深入探討這個話題(我建議所有的開發人員都熟悉這個話題),我喜歡A Primer on Bézier Curves。可以瀏覽它,只需閱讀目前您感興趣的部分即可。但是對這組函數的基本瞭解將大大減少Core Graphics中繪製的魔法,並使UIBezierPath成爲一個工具而不是黑盒子。

+0

請注意,如果您的目標是創建位於一系列點上的平滑曲線,Catmull-Rom樣條曲線可能是比Bezier更好的選擇曲線。它們在計算上比Beziers更昂貴,它們創建了一條穿過所有控制點的曲線。然而,就像貝塞爾曲線一樣,如果你試圖曲線過於劇烈,你可能會在Catmull-Rom樣條中產生「扭結」。如果您有興趣,Erica Sadun出色的iOS Developer's Cookbook書籍中有Catmull-Rom樣條的工作代碼。 –

+0

Catmull-Rom樣條曲線令人沮喪的是它們不包含第一個和最後一個點,因此您需要在「正確的位置」添加額外的擴展點。如果使用向心C-R樣條曲線(這是我通常偏好的原因),可以避免扭曲。 –

0

令路徑= UIBezierPath()

 let p1 = CGPointMake(0,self.view.frame.height/2) 
     let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2) 

     path.moveToPoint(p1) 
     path.addQuadCurveToPoint(p3, controlPoint: CGPoint(x: self.view.frame.width/2, y: -self.view.frame.height/2)) 

     let line = CAShapeLayer() 
     line.path = path.CGPath; 
     line.strokeColor = UIColor.blackColor().CGColor 
     line.fillColor = UIColor.redColor().CGColor 
     view.layer.addSublayer(line) 

就是這個道理:https://cdn.tutsplus.com/mobile/authors/legacy/Akiel%20Khan/2012/10/15/bezier.png你應該考慮切線概念

+0

使用該代碼確實將最大點指向頂點,但是我需要拋物線從最左邊的左邊開始,結束於最右邊的右邊,爲什麼在半個高度處開始並結束拋物線?我已經設法通過在'controlPoint'處使用'-container.bounds.height'來實現這個特定點,但是它不適用於所有類型的點。所以必須有一些動態值或乘數被添加到控制點的y值中。這就是我試圖找到的 – Eilon

1

訣竅是曲線分割成兩個部分,這樣就可以控制曲線經過的點。正如Eduardo的回答中所提到的,控制點處理切線,並且終點在曲線上。這讓你有從底部的曲線左頂部中央,然後從頂部中心到右下:

let p1 = CGPointMake(0,self.view.frame.height/2) 
let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2) 
let ctrlRight = CGPointMake(self.view.frame.width,0) 
let ctrlLeft = CGPointZero 

let bezierPath = UIBezierPath() 
bezierPath.moveToPoint(p1) 
bezierPath.addCurveToPoint(maxPoint, controlPoint1: p1, controlPoint2: ctrlLeft) 
bezierPath.addCurveToPoint(p3, controlPoint1: ctrlRight, controlPoint2: p3) 

UIColor.blackColor().setStroke() 
bezierPath.lineWidth = 1 
bezierPath.stroke() 
+0

同意這種方法,但是我認爲如果你刪除'p1'和'p3'上的'/ 2',曲線將更接近匹配請求。但是這不會是一個拋物線,因爲你要添加一條三次曲線而不是四條曲線。 (但是,您可以將它製成「拋物線狀」,這可能是真正的目標。) –