Dessiner courbe au fur et à  mesure

CeetixCeetix Membre
06:24 modifié dans API AppKit #1
Sur les conseil d'AliGator j'ouvre un nouveau post sur une technique d'animation que j'aimerai tester.

Pour ma petit application de graphe je trace mes arc en courbe d'un seul coup ainsi :

<br />-(void)drawArc:(Arc *)a<br />{<br />	NSBezierPath *tracer = [NSBezierPath bezierPath];<br />	[tracer moveToPoint:(NSMakePoint(a.d.abs + a.d.diametre/2,a.d.ord+ a.d.diametre/2))];<br />	[tracer curveToPoint:(NSMakePoint(a.a.abs+ a.a.diametre/2,a.a.ord+ a.a.diametre/2)) <br />		&nbsp;  controlPoint1:(NSMakePoint(a.d.abs,a.d.abs)) <br />		&nbsp;  controlPoint2:(NSMakePoint(a.a.abs,a.a.ord))];<br />	[a.couleur set];<br />	[tracer stroke];<br />	<br />	[self setNeedsDisplay:YES];<br />}<br />


J'aimerai bien animer le tracer, que mon arc soit tracé petit à  petit.
Une ou plusieurs méthodes?  :P

Réponses

  • AliGatorAliGator Membre, Modérateur
    avril 2009 modifié #2
    Alors :
    (1) Bon je reviendrais pas sur ta façon de nommer tes variables avec des noms un peu trop courts, ou d'utiliser abs et ord pour positionner tes sommets au lieu d'utiliser un NSPoint (que j'aurais utilisé d'ailleurs pour indiquer la position du centre de ton sommet et non son coin supérieur gauche)... ça me démange, mais bon :) :P

    (2) dans tes controlPoints, je pense que tu as fait une faute de frappe en créant ton point en (a.d.abs,a.d.abs), j'imagine qu'en 2e coordonnée tu voulais mettre a.d.ord plutôt ?

    (3) Tu coup si je ne me trompe pas, comme les controlPoints sont tous les deux colinéaires à  ton point de départ et ton point d'arrivée... au final ta courbe de Bézier va se résumer... à  une bête ligne ! Donc autant appeller addLineToPoint plutôt que addCurveToPoint en lui donner des controlPoints pour arriver à  ce résultat !
    Ah quoique j'ai un doute, ton controlPoint2 étant "avant" le point d'arrivée, ça va sans doute faire une petit boucle sur l'arrivée.. enfin bon faudra sûrement ajuster tes controlPoints pour faire ce que tu veux quand même... ou si tu veux une ligne, te contenter de addLineToPoint

    (4) C'est quoi ce setNeedsDisplay dans une méthode de draw ?! drawArc est appelé par drawRect j'imagine, qui boucle sur tous tes arcs et appelle la méthode drawArc en passant l'arc à  dessiner, non ?
    Donc il n'a rien à  faire dans une méthode de draw ! setNeedsDisplay est à  appeler quand on modifie notre modèle (par exemple quand tu ajoutes un sommet dans ton tableau de sommets ou un arc dans ton tableau d'arcs) pour dire à  Cocoa "hé j'ai modifié un truc dans mon modèle, va recalculer le dessin à  afficher pour dessiner tout ce bouzin".
    Si on a demandé un setNeedsDisplay à  une vue quand on a changé notre modèle, ça aura pour effet que Cocoa va appeler la méthode drawRect sur cette vue quand il en sera à  dessiner ton interface à  l'écran pour recalculer la façon de dessiner cette vue... et mémoriser cette façon de dessiner dans sa mémoire pour pas avoir à  refaire les calculs à  chaque fois... sauf si tu lui demandes de les refaire avec setNeedsDisplay...

    ---

    Une fois que tout ceci sera mis au clair, cela dépend ensuite de l'animation que tu veux.
  • AliGatorAliGator Membre, Modérateur
    06:24 modifié #3
    Bon... la suite, quand même.

    Pour ce qui est de l'animation, si je suppose que ton BezierPath est en fait une ligne, semble-t-il... les premières solutions qui me viennent à  l'esprit grossièrement, c'est d'utiliser une animation de type "balayage" ("wipe").

    Cela va révéler ton nouveau dessin progressivement par exemple de la gauche vers la droite (ou du haut vers le bas, ou...) : au début tu as ton "ancienne version" sans le bezier path, et au fur et à  mesure la partie gauche de ta vue affiche la nouvelle version (tandis que sur la partie droite c'est encore l'ancienne version)... jusqu'à  ce que toute ta vue affiche la "nouvelle version" de ton dessin avec le bezierpath dessiné.

    C'est une solution des plus simple car en plus elle utilise des effets CoreAnimation dédiés. Tu peux déjà  commencer par ça, j'ai pas le temps ce soir pour te décortiquer tout ça, mais en gros je pense qu'il faut utiliser pour ce genre d'effet une CATransition, avec le type "kCATransitionReveal" et le subtype indiquant le sens de ta transition (typiquement kCATransitionFromLeft par exemple).

    Ensuite tu installes ton animation (méthode [tt]addAnimation: forKey:[/tt]) sur le CALayer de ta vue (il faut avoir coché la vue pour laquelle tu veux animer le layer dans la partie "Wants Core Animation Layer" de l'Effects Inspector dans IB, sinon elle n'aura pas de layer CoreAnimation... ou alors faut le créer par code mais bon) au moment où tu veux faire jouer ta transition.

    Déjà  essaye ça, ça sera un bon début : créer ta CATransition, et ajouter cette animation au layer de ta vue quand tu veux la déclancher typiquement juste à  la fin du code où tu as ajouté ton arc... (je parle de la méthode qui ajoute l'arc dans le modèle, donc typiquement qui fait un addObject d'un Arc* dans ton "tableauArcs" puis appelle setNeedsDisplay juste après pour demander un recalcul du dessin... bah faut rajouter l'animation dans cette méthode, et tant qu'à  rester logique avant le setNeedsDisplay.)


    ----

    Bon cette méthode a l'avantage d'être un bon début pour commencer CoreAnimation, mais ce n'est pas une réelle animation du dessin de l'arc, dans le sens où la transition "reveal" va de la gauche vers la droite, elle ne suit pas le bezierpath que tu vas dessiner par ton arc... donc ça donnera quand même grosso modo l'effet voulu... si ton path ne revient pas sur ses pas avec une boucle (mais si c'est une ligne c'est bon).
    Mais bon on pourra revenir sur ces petits détails après, commençons soft.
  • schlumschlum Membre
    06:24 modifié #4
    dans 1239057492:

    Alors :
    [...]

    ---

    Une fois que tout ceci sera mis au clair, cela dépend ensuite de l'animation que tu veux.


    C'est bien... Maà®trise des bases avant de se prendre la tête sur des trucs user-friendly inutiles et compliqués  :P
  • schlumschlum Membre
    avril 2009 modifié #5
    Pour ce qui est de faire une sous-courbe de Bézier, tout est question de calculs mathématique, mais ça ne devrait pas être trop complexe je pense.

    Des p'tits coups de dérivées, d'interpolations... Tu vois, les maths c'est important pour faire des trucs inutiles parfois  :)
  • CeetixCeetix Membre
    06:24 modifié #6
    Merci les mecs. Oui enfin les dérivés ca va ^^.
    Sinon tu parles d'interpolation c'est comme dans flash donc? On calcule le nombre de point necéssaire au tracé etc ...
  • AliGatorAliGator Membre, Modérateur
    06:24 modifié #7
    Bah implémente déjà  ma méthode, qui a l'avantage d'être simple côté animation... mais a l'inconvénient de faire une transition de type balayage... si ton path est une ligne, ça va, si c'est un BezierPath qui tourne dans tous les sens, un balayage ne va pas donner l'effet d'animation que tu espères, mais ça sera un bon début.

    Après si tu veux vraiment animer le tracer du BezierPath plus tard, oui là  va falloir mettre la main à  la pâte côté maths. Car je ne crois pas qu'il y ait d'animation existante pour faire ça... Donc il va falloir que tu animes le point d'arrivée de ton Path dans ce cas, je ne vois pas d'autre solution dans l'immédiat...
    ... et pour que ça fasse l'effet voulu, il va falloir connaà®tre les coordonnées de chaque point de ta courbe de bézier finale (et pas juste laisser Cocoa la tracer tout seul quoi)... mais aussi sa dérivée en tout point, en fonction de tes points de contrôle choisis au point de départ et d'arrivée, pour pouvoir calculer les points de contrôle intermédiaire à  utiliser lors de ton animation.

    Et ça c'est déjà  moins de la tarte...
  • CeetixCeetix Membre
    06:24 modifié #8
    D'accord Ali merci pour toutes ces explications.
  • schlumschlum Membre
    avril 2009 modifié #9
    dans 1239083724:

    Merci les mecs. Oui enfin les dérivés ca va ^^.
    Sinon tu parles d'interpolation c'est comme dans flash donc? On calcule le nombre de point necéssaire au tracé etc ...


    En fait tu as beaucoup de chance... J'y ai réfléchi un peu en dormant, et ce sont des maths à  peine niveau lycée  :P (système d'équations à  4 inconnues, mais déjà  linéarisé !)

    En gros, on cherche les point Q0, Q1, Q2, Q3 tels que :

    P0*(1-x*u)^3+3*P1*(x*u)*(1-x*u)^2+3*P2*(x*u)^2*(1-x*u)+P3*(x*u)^3
    =
    Q0*(1-u)^3+3*Q1*u*(1-u)^2+3*Q2*u^2*(1-u)+Q3*u^3

    Ce qui donne quand on assimile les degrés équivalents :
    * Q0 = P0 (logique...)
    * Q1 = x*(P1-P0)+Q0
    * Q2 = x^2*(P2-2*P1+P0)+2*Q1-Q0
    * Q3 = x^3*(P3-3*P2+3*P1-P0)+3*Q2-3*Q1+Q0

    Tu as donc les 4 points de la nouvelle courbe de Bézier qui est la sous-courbe de la principale en x (en faisant varier x de 0 à  1)

    Petit veinard !  :)


    Petite vérification...

    Pour x = 0 :

    Q0 = P0
    Q1 = P0
    Q2 = P0
    Q2 = P0
    Pourquoi pas...

    Pour x = 1 :

    Q0 = P0
    Q1 = P1
    Q2 = P2
    Q3 = P3
    Bien !

    [Edit] Correction avec les facteurs 3 !
  • schlumschlum Membre
    avril 2009 modifié #10
    J'ai dû faire une p'tite erreur de calcul car mon programme test ne fonctionne pas, vais devoir revoir ça...  :o

    [Edit] Quel niouf... j'ai oublié les coefficients 3 dans les calculs pour la courbe paramétrique...
  • CeetixCeetix Membre
    06:24 modifié #11
    tes points P et ton vecteur u ils reprentent quoi?
  • AliGatorAliGator Membre, Modérateur
    avril 2009 modifié #12
    Bah sans avoir regardé les équations des path, j'imagine logiquement que les points P représentent les 4 points caractéristiques du BezierPath (P0 point de départ, P1 point de contrôle en P0, P2 point de contrôle en P3, P3 point d'arrivée)

    - u semble être l'abscisse curviligne du bezierPath [EDIT]ah non plutôt le vecteur unitaire[/EDIT]

    - x est une variable que tu vas varier de 0 (tout début du bezierPath, rien de construit) à  1 (bezierPath complet), les valeurs intermédiaires te permettant de faire avancer dans le path.

    Les points Q sont les points intermédiaires du bezierPath si on ne construit le bezierPath que de x%... donc en x=1 on veut Pi=Qi, en x=0 tout le monde au même endroit donc Q0=P0 et Q3=P0 aussi (départ et arrivée confondus)... et les Qi intermédiaires pour un x donné te permettent de construire ton bezierPath avec ces 4 controlPoints au fur et à  mesure que x avance.

    [EDIT] En fait plutôt que l'abscisse curviligne, u représente plutôt le vecteur unitaire (1,1). Les Pi et Qi étant des vecteurs (x,y) (... enfin des points de l'espace vectoriel quoi), tu retrouves dans les équations de schlum en fait une double-équation à  chaque fois, qui donne à  la fois les valeurs pour x et pour y, en raisonnant directement dans l'espace vectoriel |R².
  • schlumschlum Membre
    avril 2009 modifié #13
    J'ai corrigé et fait un exemple que je joins...

    4 clics sur la fenêtre...
    * Point de départ
    * Point d'arrivée
    * Contrôle 1
    * Contrôle 2
    -> L'animation commence

    Un 5e clic et tout s'efface pour revenir à  la configuration au lancement
  • schlumschlum Membre
    avril 2009 modifié #14
    Purée, je viens de remarquer que j'écrivais partout Bézier avec un 's' comme la ville de Béziers...
    Désolé Monsieur Pierre Bézier  :o

    Au fait, tous les calculs ci-dessus tiennent parce qu'une courbe de Bezier est une courbe paramétrique polynomiale de degré 3 et qu'une partie de cette courbe en est forcément une aussi (composition avec une fonction linéaire...)
  • CeetixCeetix Membre
    06:24 modifié #15
    Ok Ali pour tes explications je comprends. Schlum j'ai regardé ton exemple c'est vraiment ça. Je pense souvent à  mon code pendant la nuit mais pas à  ton stade. ^^

    Lol faut pas déprimer pour un 's'  :P
  • schlumschlum Membre
    avril 2009 modifié #16
    Ah non, mais pendant la nuit j'ai juste pensé que c'était une composition linéaire et que ça donnerait un système d'équations, j'ai pas fait les calculs de tête  :) (d'ailleurs la linéarité du système d'équations était une bonne surprise, quoique logique au vu de la gueule du truc).

    PS : vous aurez remarqué que c'est fait à  la va vite, il y a des erreurs mémoire sur le NSTimer  :o (il manque 1 retain et un reset à  nil...)

    Allez, pour la peine, je corrige, je rajoute la gestion de la barre d'espace pour prendre des points au pif, et je mets la nouvelle version !
  • AliGatorAliGator Membre, Modérateur
    06:24 modifié #17
    dans 1239116752:

    Ah non, mais pendant la nuit j'ai juste pensé que c'était une composition linéaire et que ça donnerait un système d'équations, j'ai pas fait les calculs de tête  :) (d'ailleurs la linéarité du système d'équations était une bonne surprise, quoique logique au vu de la gueule du truc).

    PS : vous aurez remarqué que c'est fait à  la va vite, il y a des erreurs mémoire sur le NSTimer  :o (il manque 1 retain et un reset à  nil...)

    Allez, pour la peine, je corrige, je rajoute la gestion de la barre d'espace pour prendre des points au pif, et je mets la nouvelle version !
    J'avoue que j'ai pas téléchargé ni lu ton code, mais un timer ça se retain rarement : dès l'instant où tu le schedule sur ta runloop, cette dernière le retain. Donc en général [NSTimer scheduledTimerWithInterval:...] suffit, sans même récupérer le timer retourné, puisqu'il est alors déjà  installé sur la runloop et retenu par cette dernière.
  • CeetixCeetix Membre
    06:24 modifié #18
    Merci Schlum pour cette petite démo bien sympa !
  • schlumschlum Membre
    06:24 modifié #19
    dans 1239127728:

    dans 1239116752:

    Ah non, mais pendant la nuit j'ai juste pensé que c'était une composition linéaire et que ça donnerait un système d'équations, j'ai pas fait les calculs de tête  :) (d'ailleurs la linéarité du système d'équations était une bonne surprise, quoique logique au vu de la gueule du truc).

    PS : vous aurez remarqué que c'est fait à  la va vite, il y a des erreurs mémoire sur le NSTimer  :o (il manque 1 retain et un reset à  nil...)

    Allez, pour la peine, je corrige, je rajoute la gestion de la barre d'espace pour prendre des points au pif, et je mets la nouvelle version !
    J'avoue que j'ai pas téléchargé ni lu ton code, mais un timer ça se retain rarement : dès l'instant où tu le schedule sur ta runloop, cette dernière le retain. Donc en général [NSTimer scheduledTimerWithInterval:...] suffit, sans même récupérer le timer retourné, puisqu'il est alors déjà  installé sur la runloop et retenu par cette dernière.


    Je sais cela, mais j'ai gardé l'habitude de faire un retain sur un NSTimer, considérant illogique qu'il soit retenu par la runloop...  ;) (et aussi qu'il retienne sa cible...)
    ça ne coûte pas grand chose  :)
Connectez-vous ou Inscrivez-vous pour répondre.