[Résolu] Manipuler les courbes de bezier

busterTheobusterTheo Membre
mars 2015 modifié dans API UIKit #1

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

  • MalaMala Membre, Modérateur

    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


  • fleurantinfleurantin Membre
    mars 2015 modifié #3
    Personnellement je développe une appli de topographie sur MacOS et j'utilise les NSBezierPath où tout est prévu et j'en suis fort content.
  • busterTheobusterTheo Membre
    mars 2015 modifié #5

    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.


  • CéroceCéroce Membre, Modérateur
    @busterTheo Franchement, tout ce que j'aurais éventuellement à  redire sur ton code concerne le fait qu'il n'y a pas de couche Modèle. Par exemple, ça peut poser problème si tu veux enregistrer dans des fichiers.
    Mais ton utilisation de UIBezierPath est bonne, alors inutile de te prendre la tête.
  • MalaMala Membre, Modérateur

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


     




    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.




    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. :p


  • 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 ?



    case .Changed:
    let recognizerX = recognizer.locationInView(self.view).x as CGFloat
    let recognizerY = recognizer.locationInView(self.view).y as CGFloat

    pointP1.frame.origin.x = pointP1.frame.origin.x + recognizerX - pointStart.frame.origin.x
    pointP1.frame.origin.y = pointP1.frame.origin.y + recognizerY - pointStart.frame.origin.y

    pointStart.frame.origin.x = recognizerX
    pointStart.frame.origin.y = recognizerY
  • 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.

  • busterTheobusterTheo Membre
    mars 2015 modifié #14

    Salut fleurantin, j'ai pas compris 



     


    donc créer une catégorie



    Et puis, j'ai cherché partout, j'ai rien trouvé sur



     


     


    intégrer directement UIBezierPath dans ton modèle CoreData

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



  •  


     


    intégrer directement UIBezierPath dans ton modèle CoreData

    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.


     



     


     


    donc créer une catégorie.

    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/


Connectez-vous ou Inscrivez-vous pour répondre.