2016-08-02 32 views
2

Ich versuche, eine einfache Parabelform mit UIBezierPath zu zeichnen. Ich habe eine maxPoint und eine boundingRect, von denen ich die Breite und Ausdehnung der Parabel basiere.
Hier ist die Funktion, die ich machte die Parabel zu zeichnen (I die Parabel in einem Container Ansicht ziehen, wird rectcontainer.bounds sein):Unstanding UIBezierPath Krümmungsmechanismus, ControlPoint und der Kurvenpunkt

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

Mein Problem ist, dass ich die maxPoint wollen, dass ich in der Funktion senden sei der eigentliche Extrempunkt in der Parabel. Wenn ich zum Beispiel (CGRectGetMidX(container.bounds), 0) einsende, sollte der maximale Punkt in der obersten Mitte sein. Aber diese Funktion bei der Verwendung mit diesem speziellen Punkt, das ist, was das Ergebnis wie folgt aussieht:

enter image description here

Was genau der Weg hier tut? Oder mit anderen Worten, wie kann ich vom controlPoint zum tatsächlichen maximalen Punkt kommen, den ich benötige? Ich habe versucht, verschiedene Werte aus dem y Wert, basierend auf der Höhe der boundingRect, zu addieren und zu subtrahieren, aber ich konnte nicht die richtige Kombination finden, da in verschiedenen Punkten mit anderen Werten verhält es sich anders als y. Es scheint eine Art Multiplikator hinzukommen, wie kann ich das lösen?

Antwort

4

für Mai Anwendungen adam.wulf Lösung ist in Ordnung, aber es nicht wirklich eine Parabel erstellen. Um eine Parabel zu erstellen, müssen wir den Kontrollpunkt unter Berücksichtigung des Mittelpunkts der quadratischen Kurve berechnen. Bézier Wege sind nur Mathematik; Das können wir relativ einfach berechnen. Wir müssen nur die Bézier-Funktion invertieren und sie für t = 0.5 lösen.

Die Bézier-Lösung bei 0,5 (der Mittelpunkt) ist schön bei Draw a quadratic Bézier curve through three given points abgeleitet.

2*Pc - P0/2 - P2/2 

Wo Pc ist der Punkt, den wir gehen wollen durch und P0 und P2 die Endpunkte sind.

(Berechnung der Bézier an anderen Punkten ist nicht sehr intuitiv. Der Wert bei t = 0,25 ist nicht "ein Viertel des Weges entlang." Aber glücklicherweise für unsere Zwecke, t = 0.5 passt ziemlich gut zu unserer Intuition von "dem Mittelpunkt" auf einem quadratischen.)

Angesichts unserer Lösung können wir unseren Code schreiben. Verzeiht die Übersetzung zu Swift 3; Meine Kopie von Xcode 7.3 ist nicht sehr glücklich mit iOS-Spielplätzen, aber es sollte einfach zu 2.2 zu konvertieren sein.

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 
} 

Die halfPoint1D Funktion ist die die eindimensionale Implementierung unserer Lösung.Für unser zweidimensionales CGPoint müssen wir es nur zweimal aufrufen.

Wenn ich nur eine Ressource für das Verständnis von Bézier-Kurven empfehlen könnte, wäre es wahrscheinlich der "Constructing Bézier curves" Abschnitt von Wikipedia. Studieren die kleinen Animationen, die zeigen, wie die Kurven zustande kommen finde ich sehr aufschlussreich. Der Abschnitt "Spezielle Fälle" ist ebenfalls hilfreich. Für eine gründliche Erkundung des Themas (und eines, das ich allen Entwicklern empfehlen kann), mag ich A Primer on Bézier Curves. Es ist in Ordnung, es zu überfliegen und nur die Teile zu lesen, die Sie im Moment interessieren. Aber ein grundlegendes Verständnis dieser Gruppe von Funktionen wird ein langer Weg sein, um die Magie aus dem Zeichnen in Core Graphics zu entfernen und UIBezierPath zu einem Werkzeug anstatt zu einer Blackbox zu machen.

+0

Wenn Sie eine glatte Kurve erstellen möchten, die auf einer Reihe von Punkten liegt, sind Catmull-Rom-Splines möglicherweise eine bessere Wahl als Bezier Kurven. Sie sind rechenintensiver als Beziers, sie erzeugen eine Kurve, die alle Kontrollpunkte durchläuft. Wie Bezier-Kurven können Sie jedoch in Catmull-Rom-Splines "Knicke" bekommen, wenn Sie versuchen, zu stark zu krümmen. Erica Sadun's ausgezeichnete iOS-Entwickler-Kochbuch Bücher hat Arbeitscode für Catmull-Rom Splines, wenn Sie interessiert sind. –

+0

Eine frustrierende Sache bei Catmull-Rom-Splines ist, dass sie nicht den ersten und letzten Punkt enthalten, also müssen Sie zusätzliche Erweiterungspunkte an den "richtigen Stellen" hinzufügen. Sie können Knicke vermeiden, wenn Sie zentripetale C-R-Splines verwenden (was meine übliche Präferenz für genau diesen Grund ist). –

0

let path = 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) 

dies der Grund ist: https://cdn.tutsplus.com/mobile/authors/legacy/Akiel%20Khan/2012/10/15/bezier.png Sie sollten die Tangente Konzept

+0

Nachdem ich diesen Code benutzt habe, bringt er den maximalen Punkt nach oben, aber ich brauche die Parabel, um am unteren linken Ende zu beginnen und am untersten rechts, warum startest und endest du die Parabel auf halber Höhe? Es ist mir gelungen, es mit diesem speziellen Punkt so zu machen, dass ich '-container.bounds.height' am' controlPoint' habe, aber es funktioniert nicht mit allen Arten von Punkten. Daher muss ein dynamischer Wert oder ein Multiplikator zum y-Wert des Kontrollpunkts hinzugefügt werden. Dies ist, was ich versuche zu finden – Eilon

1

Der Trick betrachten müssen, ist die Kurve in zwei Teile zu spalten, so dass Sie steuern können welche Punkte die Kurve durchläuft. Wie in Eduardos Antwort erwähnt, behandeln die Kontrollpunkte die Tangente und die Endpunkte befinden sich auf der Kurve. Auf diese Weise können Sie eine Kurve von unten nach oben in der Mitte links, dann von oben in der Mitte nach rechts unten:

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

Vereinbart mit dem Ansatz, aber ich denke, die Kurve wird viel mehr entsprechen die Anfrage, wenn Sie die '/ 2' auf' p1' und 'p3' entfernen. Dies ist jedoch keine Parabel, da Sie eher eine kubische Kurve als eine Quad-Kurve hinzufügen. (Sie können es jedoch "parabelartig" machen, was das eigentliche Ziel sein könnte.) –