[Résolu] Manipuler les courbes de bezier
Bonjour,
depuis plusieurs jours, je tente de manipuler ces foutues courbes, et là , je pense avoir une petite piste sympa. J'attend d'ailleurs les remarques. Je fais certainement, comme d'habitude, des erreurs de conception, mais c'est pas mal quand même. En tout cas, je suis assez content. Je vais pouvoir aller plus loin, car je dois manipuler des formes plus abouties qu'une simple courbe : formes fermées avec de nombreux points de contrôle. Tous les tutos que j'ai vu ne m'ont pas vraiment aidé, ils m'ont plutôt embrouillé. J'ai du ressortir mes cours de math et d'illustrator.
En tout cas, je met ici une petite vidéo explicative, ainsi que le code ci-dessous (Le storyBoard est vide ) - Si ça peut aider quelqu'un.
http://www.magnitude-6.net/ge/e.mov
Le ViewController
import UIKit
class ViewController: UIViewController {
// Mark: - Les objets
// La courbe rouge
var curve: Curve!
// Le point bleu symbolisant le début de la courbe
var pointStart: Point!
// Le point bleu symbolisant la fin de la courbe
var pointEnd: Point!
// Le point bleu symbolisant le Point de contrôle N° 1 de la courbe
var pointP1: Point!
// Le point bleu symbolisant le Point de contrôle N° 2 de la courbe
var pointP2: Point!
// Mark: - Les variables
// Le début de la courbe
var startCurveX = CGFloat(100)
var startCurveY = CGFloat(100)
// Le Point de contrôle N° 1 de la courbe
var aCurveP1X = CGFloat(100)
var aCurveP1Y = CGFloat(250)
// Le Point de contrôle N° 2 de la courbe
var aCurveP2X = CGFloat(300)
var aCurveP2Y = CGFloat(250)
// La fin de la courbe
var aEndCurveX = CGFloat(300)
var aEndCurveY = CGFloat(100)
// Le point bleu symbolisant le début de la courbe
var pointStartX = CGFloat(296)
var pointStartY = CGFloat(296)
// Le point bleu symbolisant la fin de la courbe
var pointEndX = CGFloat(496)
var pointEndY = CGFloat(296)
// Le point bleu symbolisant le Point de contrôle N° 1 de la courbe
var pointP1X = CGFloat(296)
var pointP1Y = CGFloat(396)
// Le point bleu symbolisant le Point de contrôle N° 2 de la courbe
var pointP2X = CGFloat(496)
var pointP2Y = CGFloat(396)
// Mark: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
curve = Curve(frame: CGRectMake(200,200,400,400))
self.view.addSubview(curve)
curve.startCurveFloatX = startCurveX
curve.startCurveFloatY = startCurveY
curve.aCurveP1FloatX = aCurveP1X
curve.aCurveP1FloatY = aCurveP1Y
curve.aCurveP2FloatX = aCurveP2X
curve.aCurveP2FloatY = aCurveP2Y
curve.aEndCurveFloatX = aEndCurveX
curve.aEndCurveFloatY = aEndCurveY
pointStart = Point(frame: CGRectMake(pointStartX,pointStartY,8,8))
self.view.addSubview(pointStart)
pointEnd = Point(frame: CGRectMake(pointEndX,pointEndY,8,8))
self.view.addSubview(pointEnd)
pointP1 = Point(frame: CGRectMake(pointP1X,pointP1Y,8,8))
self.view.addSubview(pointP1)
pointP2 = Point(frame: CGRectMake(pointP2X,pointP2Y,8,8))
self.view.addSubview(pointP2)
// Le pan du pointStart
let panGesturePointStart: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: "pointStartPan:")
panGesturePointStart.maximumNumberOfTouches = 1
panGesturePointStart.minimumNumberOfTouches = 1
pointStart.addGestureRecognizer(panGesturePointStart)
pointStart.userInteractionEnabled = true
// Le pan du pointEnd
let panGesturePointEnd: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: "pointEndPan:")
panGesturePointEnd.maximumNumberOfTouches = 1
panGesturePointEnd.minimumNumberOfTouches = 1
pointEnd.addGestureRecognizer(panGesturePointEnd)
pointEnd.userInteractionEnabled = true
}
// Mark: - PanGestureRecognizer
// PanGestureRecognizer du pointStart
func pointStartPan(recognizer: UIPanGestureRecognizer) {
let pan = recognizer as UIPanGestureRecognizer
switch(pan.state) {
case .Began:
let recognizerX = recognizer.locationInView(self.view).x as CGFloat
let recognizerY = recognizer.locationInView(self.view).y as CGFloat
case .Changed:
let recognizerX = recognizer.locationInView(self.view).x as CGFloat
let recognizerY = recognizer.locationInView(self.view).y as CGFloat
pointStart.frame.origin.x = recognizerX
pointStart.frame.origin.y = recognizerY
curve.startCurveFloatX = recognizerX + startCurveX - pointStartX
curve.startCurveFloatY = recognizerY + startCurveY - pointStartY
pointP1.frame.origin.x = recognizerX
pointP1.frame.origin.y = recognizerY + pointP1Y - pointStartY
curve.aCurveP1FloatX = pointP1.frame.origin.x + aCurveP1X - pointP1X
curve.aCurveP1FloatY = pointP1.frame.origin.y + aCurveP1Y - pointP1Y
curve.setNeedsDisplay()
case .Ended:
let recognizerX = recognizer.locationInView(self.view).x as CGFloat
let recognizerY = recognizer.locationInView(self.view).y as CGFloat
pointStart.frame.origin.x = recognizerX
pointStart.frame.origin.y = recognizerY
default:
break
}
}
// PanGestureRecognizer du pointEnd
func pointEndPan(recognizer: UIPanGestureRecognizer) {
let pan = recognizer as UIPanGestureRecognizer
switch(pan.state) {
case .Began:
let recognizerX = recognizer.locationInView(self.view).x as CGFloat
let recognizerY = recognizer.locationInView(self.view).y as CGFloat
case .Changed:
let recognizerX = recognizer.locationInView(self.view).x as CGFloat
let recognizerY = recognizer.locationInView(self.view).y as CGFloat
pointEnd.frame.origin.x = recognizerX
pointEnd.frame.origin.y = recognizerY
curve.aEndCurveFloatX = recognizerX + aEndCurveX - pointEndX
curve.aEndCurveFloatY = recognizerY + aEndCurveY - pointEndY
pointP2.frame.origin.x = recognizerX
pointP2.frame.origin.y = recognizerY + pointP2Y - pointEndY
curve.aCurveP2FloatX = pointP2.frame.origin.x + aCurveP2X - pointP2X
curve.aCurveP2FloatY = pointP2.frame.origin.y + aCurveP2Y - pointP2Y
curve.setNeedsDisplay()
case .Ended:
let recognizerX = recognizer.locationInView(self.view).x as CGFloat
let recognizerY = recognizer.locationInView(self.view).y as CGFloat
pointEnd.frame.origin.x = recognizerX
pointEnd.frame.origin.y = recognizerY
default:
break
}
}
}
La courbe
import UIKit
class Curve: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.yellowColor()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Mark: - Les variables
var startCurveFloatX: CGFloat = 0.0
var startCurveFloatY: CGFloat = 0.0
var aCurveP1FloatX: CGFloat = 0.0
var aCurveP1FloatY: CGFloat = 0.0
var aCurveP2FloatX: CGFloat = 0.0
var aCurveP2FloatY: CGFloat = 0.0
var aEndCurveFloatX: CGFloat = 0.0
var aEndCurveFloatY: CGFloat = 0.0
// Mark: - La méthode drawRect
override func drawRect(myRect: CGRect) {
var startCurveX = startCurveFloatX
var startCurveY = startCurveFloatY
var aCurveP1X = aCurveP1FloatX
var aCurveP1Y = aCurveP1FloatY
var aCurveP2X = aCurveP2FloatX
var aCurveP2Y = aCurveP2FloatY
var aEndCurveX = aEndCurveFloatX
var aEndCurveY = aEndCurveFloatY
var Path: UIBezierPath = UIBezierPath(rect:myRect)
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 2.0)
CGContextSetStrokeColorWithColor(context, UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0/255.0, alpha: 1.0).CGColor)
CGContextMoveToPoint(context, startCurveX, startCurveY)
CGContextAddCurveToPoint(context, aCurveP1X, aCurveP1Y, aCurveP2X, aCurveP2Y, aEndCurveX, aEndCurveY)
CGContextStrokePath(context)
}
}
Les points
import UIKit
class Point: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// // Pas de variables transmises, contrairement à Curve.
// Le positionnement des points se fait dans le ViewController.
//
// Mark: - La méthode drawRect
override func drawRect(myRect: CGRect) {
var pointPath:UIBezierPath = UIBezierPath(ovalInRect: CGRectMake(0, 0, 8, 8))
UIColor.blueColor().setFill()
pointPath.fill()
}
}
Réponses
Si tu veux vraiment aller vers du plus complexe, je t'encouragerais à regarder du côté des polybezier. C'est d'ailleurs dommage qu'Apple ne l'intègre pas d'entrée de jeu.
http://www.eliotis.com/videos/egg.mp4
Voici un exemple de rendu.
Merci Mala pour les polyBezier, mais je n'y comprend pas grand chose.
Et oui, fleurantin, c'est tout-à -fait ce que je cherche à faire. J'ai cru comprendre que NSBezierPath était pour les applis sur MacOsx, et UIBezierPath pour les applis IOSx. Et t'aurais pas une petite critique sur mon approche, ou/et un bout de code, ou/et une notion type concept... Merci d'avance.
DrawKit : https://github.com/DrawKit/DrawKit
gère ce genre de courbe.
En général il y a un trait reliant les poignées d'une extrémité.
Merci Eric P.
C'est tout en objectiveC - ça va être dur pour moi...
En plus c'est pour mac, je ne sais pas comment le lire sur le simulateur smartphone/tablette.
Et y'a 241 erreurs.. Impossible de le lire.
J'ai bien essayer de déchiffrer les fichiers, mais y'a un million de fichiers et c'est un peu du chinois pour moi.
T'aurais pas une autre idée, sans abuser, car j'ai cherché, je ne trouve pas grand chose, et donc, je fais à ma façon, comme le code que j'ai mis plus haut.
Mais ton utilisation de UIBezierPath est bonne, alors inutile de te prendre la tête.
Pour culture, les poly-béziers c'est ce que fait fleurantin dans sa capture d'écran. Ce sont des courbes de bézier mises bout à bout et dont les points de contact ont des vecteurs opposés (ce qui donnent cette continuité de tracé tout en arrondit).
En même temps, Swift est très jeune. La littérature et les projets communautaires sont encore limités.
Donc comme l'indique Céroce, réfléchir à une couche modèle est peut-être l'étape suivante pour faciliter la manipulation. De là à gérer non pas une seule courbe de bézier dans son modèle mais une suite de courbes et on est aux poly-béziers.
Merci Céroce, pour tes encouragements. ça me rassure. D'ailleurs, j'avance bien sur mes petites poignées, c'est super sympa à faire, je m'régale.
Sans quoi, concernant la couche modèle, tous ces petits essais (bezier, clicks photos, contours, etc.) que je fais sont destinés à être intégrés dans un projet complet, dont j'ai déjà monté la structure. Et dedans, j'y ai déjà inclus coreData avec le code qu'Apple donne lorsque l'on coche la boite "coreData" lors d'un nouveau projet. Est-ce que c'est ce à quoi tu fais allusion ? Parce que, évidemment, je devrais stocker toutes ces données. J'ai pour l'instant un .xcdatamodel avec 8 champs, et ça va augmenter environ jusqu'à une centaine (position poignées, noms de photos, coordonnées diverses, angles, etc.)
Je te met une vidéo du gabarit de l'appli dans laquelle j'intègrerai tout ça.
http://www.magnitude-6.net/ge/z.mov
Encore merci
Ah, merci Mala, ouais, de toutes façons je dois faire des formes fermées, j'aurais donc plusieurs courbes, et effectivement je devrais tout stocker dans la bdd (coreData). Aussi, créer une longue liste de variables ou plutôt un tableau qui va bien... Je ne suis pas rendu, mais je m'accroche, et grâce au bar, j'avance bien, et je prend un peu la confiance.
J'ai donc avancé sur mes courbes de Bezier, je remet en ligne une vidéo plus poussée.
http://www.magnitude-6.net/ge/f.mov
Mes tangentes fonctionnent bien.
J'ai du modifier un peu le code pour qu'après avoir bougé les tangentes, lorsque l'on bouge les points de départ et de fin, elles restent en place. En fait, j'ai juste modifié la position des points simulant les tangentes (pointP1), et je les ai déplacé avant les points de départ (pointStart) et de fin.
Je met juste un bout de code, pour pas saturer, le reste, ça suit.
J'en suis donc à 4 function (recognizer: UIPanGestureRecognizer).
Lorsque j'aurai plein de formes fermées, et donc pleins de points de départ, fin, contrôle, etc, je risque d'avoir une cinquantaine de fonction.
C'est pas un peu débile ?
J'imagine qu'il ne faudrait n'en faire qu'une, avec un switch peut-être ?
Pour info un UIBezierPath peut comprendre une multitude d'éléments jointifs ou pas.
Personnellement je considère qu'il faut simplement ajouter des méthodes à UIBezierPath (recherche du point en XY, liste des points de contrôles, draw, pointSurLigne, etc) donc créer une catégorie.
Tu peux intégrer directement UIBezierPath dans ton modèle CoreData sans te soucier de sauvegarder les points et autres éléments qui le compose.
Salut fleurantin, j'ai pas compris
Et puis, j'ai cherché partout, j'ai rien trouvé sur
Dans le dataModel, y'a que des int, string, boolean, date, binary data, et transformable.
J'ai trouvé ça mais c'est pas clair pour moi.
http://w3facility.org/question/uibezierpath-persistence-with-core-data/
Et ça, encore moins
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdNSAttributes.html
Tu peux préciser stp.
En tout cas, j'achète l'idée de bezier dans le coreData, mais pour l'instant, je ne trouve pas quoi en faire.
Encore merci...
Pour les bezier (mais aussi pour d'autres objets) tu mets un attribut Transformable et tu lui indique dans Name NSKeyedUnarchiveFromDataTransformerName qui permet à CoreData de stocker et lire un objet qui respecte le NSCoding, ce qui est le cas des NSBezierPath. C'est aussi pratique pour les NSColor.
Je voulais exprimer qu'il n'est pas nécessaire de dériver NSBezierPath car on peut lui associer des méthodes personnelles supplémentaires avec une catégorie. Je me suis fait une méthode pointSurBezier, pointSurSommet, etc
De plus quand le dis NSBezierPath je dois préciser que ce n'est pas un ensemble de courbes de bézier mais un ensemble de segment de droite, de segment de cercle, de rectangle, de segment courbe (bezier).
Voir la doc Apple ici : https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Paths/Paths.html#//apple_ref/doc/uid/TP40003290-CH206-BBCHFJJG
Merci pour tout, mais je plane un peu, j'ai donc créé un nouveau sujet.
http://forum.cocoacafe.fr/topic/13614-coredata-save-et-load-un-array/