Insérer un point dans un NSBezierPath

UniXUniX Membre
15:14 modifié dans API AppKit #1
Salut.

Je créé un NSBezierPath avec la souris (je récupère les coordonnées de chaque points dans mouseDown, et je les ajoute au NSBezierPath).

Maintenant, je souhaiterais pouvoir insérer des points dans ce NSBezierPath. L'idéal serait d'avoir une méthode qui, avec les coordonnées du point cliqué, détecte si il se trouve sur la trajectoire du NSBezierPath, et renvoie le point précédent par exemple ....

Comment faire ?

Réponses

  • fouffouf Membre
    15:14 modifié #2
    Je ne crois pas que ca soit directement intégré dans NSBezierPath. Ce que tu peux par contre faire, c'est de rechercher le point le plus près dans la liste des éléments du bezier path. Pour cela, tu peux utiliser elementCount et elementAtIndex:associatedPoints: pour récupérer les points et ensuite utiliser le code suivant pout recupérer la distance avec le point de la souris (utilisé dans GeoX) :
    <br />sqrt(pow(sp.x-fp.x,2) +pow(sp.y-fp.y,2))<br />
    

    ( fp pour le premier point, sp pour le second).

    Normalement, ca devrait suffire ;)
  • 15:14 modifié #3
    Tout petit détail, passer par une racine carrée n'est pas nécessaire: si ce que tu cherches est comparer les distances et prendre le point pour laquelle la distance est la plus courte, ce sera aussi celui pour lequel le carré de la distance est le plus faible, et donc tu épargnes quelques cycles processeur.
  • UniXUniX Membre
    15:14 modifié #4
    Ben j'avais pensé à  faire un truc dans le style, mais la méthode qui consiste à  chercher le point du NSBezierPath le plus proche de la souris ne me semble pas la bonne ....

    Imagine que tu ais un NSBezierPath avec un angle fermé comme ci-contre :bezier.png
    Si tu cliques sur le NSBezierPath à  côté du point 3, ben le point le plus proche, c'est pas le 2, mais le 1 ....
  • 15:14 modifié #5
    Si tu as des formes irrégulières comme celles que tu montres, il faut tester les points de ton bezier un à  un, et prendre celui dont la distance est la plus faible par rapport à  la position de la souris, et donc ce sera le 1.
  • UniXUniX Membre
    15:14 modifié #6
    Ben oui, mais ça va pas .....
    Le but est d'insérer un point dans le bezier. Donc, si je clique à  côté du point 3, c'est que je veux insérer un point entre le 2 et le 3, mais si je ne fais que tester le point plus proche, c'est le 1 qu'il va trouver, pas le 2 ...
  • 15:14 modifié #7
    Tu pourrais modifier et mettre en pointillé rouge par exemple le trait auquel tu veux arriver, je crois que le problème est plus complexe que ça...
  • UniXUniX Membre
    15:14 modifié #8
    Là  je comprends pas bien ce que tu veux dire ....
    Mais je suis d'accord avec toi, ça va pas être simple ... :(
  • BruBru Membre
    octobre 2005 modifié #9
    Si j'ai bien compris ton problème, tu cherches à  savoir si un point donnée est sur un point de ton bezier-path (et non pas à  l'intérieur de la figure dont le contour est le bezier-path)...

    Dans ce cas, en tant que apôtre de la non-mathémathique, voilà  ce que je te suggère :

    <br />- (void)mouseDown:(NSEvent *)theEvent<br />{<br />    int nbElem, ix;<br />    NSPoint point[10], point1, curPoint;<br />    NSBezierPath *tmpPath;<br />    BOOL pointOK;<br /><br />    nbElem=[path elementCount];<br />    curPoint=[self convertPoint:[theEvent locationInWindow] fromView:nil];<br /><br />    for (ix=0; ix&lt;nbElem; ix++)<br />    {<br />        pointOK=NO;<br />        switch ([path elementAtIndex:ix associatedPoints:point])<br />        {<br />            case NSMoveToBezierPathElement:<br />                point1=point[0];<br />                break;<br /><br />            case NSLineToBezierPathElement:<br />                tmpPath=[[NSBezierPath alloc] init];<br />                [tmpPath moveToPoint:point1];<br />                [tmpPath lineToPoint:point[0]];<br />                point1=point[0];<br />                pointOK=[tmpPath containsPoint:curPoint];<br />                [tmpPath release];<br />                break;<br /><br />        }<br />        if (pointOK) break;<br />    }<br />    NSLog(@&quot;mon point est sur un bezier-path ? %i&quot;, pointOK);<br />}<br />
    


    En fait, le code est simple : je récupère chaque élément du bezier-path. Ensuite, je construits un bezier-path temporaire ne contenant que cet élément. Via containsPoint:, je vérifie si le point de ma souris est dans ce bezier-path temporaire. Si c'est le cas, j'arrête tout, sinon, je continue en scannant les autres éléments.

    Je n'ai pas implémenté tous les NSBezierPathElement (je te laisse un peu de travail)...

    D'autre part, je ne sais pas si c'est la meilleur (ou plus efficace) méthode, mais ça devrait marcher.

    .
  • 15:14 modifié #10
    Je retire tout ce que j'ai dit... fouf, ton herbe n'est pas assez bonne et disperse l'esprit :p

    Le code de Bru est dans le genre de ce que j'allais proposer après avoir relu l'énnoncé, mais je ferais juste une remarque. La méthode containsPoint marche évidemment pour les traits, mais il faut cliquer très très précisément (en fait ça ne marche même pas à  tous les coups, vu que la position de la souris est donnée avec des valeurs entières, mais dans le cas d'une ligne non horizontale (ou verticale) et la ligne ne passe pas forcément par ces valeurs). Je pense que ce problème peut se résoudre avec un [tt][path setFlatness:1.0];[/tt] (ou éventuellement par la moitié de l'épaisseur de ton trait - la valeur par défaut est 0.6), mais je n'ai pas testé.
  • BruBru Membre
    15:14 modifié #11
    dans 1128890166:
    La méthode containsPoint marche évidemment pour les traits, mais il faut cliquer très très précisément (en fait ça ne marche même pas à  tous les coups, vu que la position de la souris est donnée avec des valeurs entières, mais dans le cas d'une ligne non horizontale (ou verticale) et la ligne ne passe pas forcément par ces valeurs).


    J'avais résolu ça en testant les 9 points (via une seconde boucle) autour du point de ma souris... Je ne l'ai pas mis, afin que UniX travaille un peu...

    .
  • fouffouf Membre
    15:14 modifié #12
    En effet, j'avais pas compris. La méthode de Bru est excellente
  • UniXUniX Membre
    15:14 modifié #13
    Super  :p

    Je vais regarder ça, et dès que j'ai un résultat, je vous tiens au courant ....!
  • UniXUniX Membre
    15:14 modifié #14
    Bon alors pour moi, ça ne fonctionne pas .... :( J'ai été jusqu'à  tester les 25 autour, pas mieux ...

    Pour trouver la solution au problème, j'ai plusieurs questions qui me viennent à  l'esprit :
    1) containsPoint teste-t'il tous les points du bezier, ou seulement ceux de construction (dans notre cas le départ et l'arrivée), c'est juste pour être sûr car la doc est pas trop claire ...
    2) les coordonnées des points du bezier sont-ils des entiers ? Car mes points de test ont des coordonnées entières, mais par exemple les points de construction (point1, point[0] dans l'exemple de Bru) ne sont pas entiers ...
  • UniXUniX Membre
    15:14 modifié #15
    Personne pour me renseigner ...??? :-\\
  • 15:14 modifié #16
    Pour la 1 c'est écrit un peu plus haut, pour la 2 les point sont en float (comme tout ce qui concerne l'affichage en fait).
  • Eddy58Eddy58 Membre
    15:14 modifié #17
    dans 1128946582:

    2) les coordonnées des points du bezier sont-ils des entiers ? Car mes points de test ont des coordonnées entières, mais par exemple les points de construction (point1, point[0] dans l'exemple de Bru) ne sont pas entiers ...

    Il est préférable de travailler en float, car les membres de la structure NSPoint sont définis en float. :) 
  • UniXUniX Membre
    15:14 modifié #18
    Bon, j'ai essayé en passant les points en float, rien de plus ...

    Quand je vais dans le debbuger, je vois les coordonnées des points de construction du bezier qui sont du style :
    x = 145.2658745&nbsp;  y = 245.1475874
    


    Si les coordonnées des points peuvent avoir des décimales, moi quand je teste mes points, c'est des coordonnées du style :
    x = 145.0&nbsp; y = 245.0
    

    donc forcemment, il ne les trouve pas identiques ....

    Mais, si c'est bien ça, comment faire ...?
  • Eddy58Eddy58 Membre
    15:14 modifié #19
    Et bien, comme suggéré plus haut, tu fais des tests dans une certaine zone autour de ton point, par exemple dans le cas présent pour 144.0<x<146.0, et 244.0<y<246.0, à  moins que j'ai mal saisis le problème. ;)
  • UniXUniX Membre
    15:14 modifié #20
    Oui mais s'il faut aller à  6 chiffres après la virgule, ça fait des centaines de milliers de tests ..... ;D
  • 15:14 modifié #21
    dans 1128890166:

    [tt][path setFlatness:1.0];[/tt]


    Tu as essayé ça (en mettant éventuellement plus que 1)?
  • 15:14 modifié #22
    dans 1128963359:

    x = 145.2658745&nbsp;  y = 245.1475874
    



    Dans ce cas au lieu de comparer x et y aux coordonnées de la souris, tu compares floorf(x), floorf(y), ceilf(x) et ceilf(y) aux coordonnées de la souris...
  • UniXUniX Membre
    15:14 modifié #23
    J'ai essayé setFlatness, ça ne fait rien de plus.
    Quand à  floorf et ceilf, je ne peux pas le faire, car je cherche à  déterminer si le clic de la souris est sur le bezier ou non avec containsPoint.

    Le désespoir me guette ... :-\\
  • Eddy58Eddy58 Membre
    15:14 modifié #24
    dans 1128964729:

    Oui mais s'il faut aller à  6 chiffres après la virgule, ça fait des centaines de milliers de tests ..... ;D

    Ce n'est pas ce que je voulais dire, mais c'est pas grave. ;)
    Va jeter un coup d'oeil ici : http://www.cocoadev.com/index.pl?HitDetection
  • BruBru Membre
    15:14 modifié #25
    Franchement, c'est vraiment pas compliqué de faire le test sur les x points autour du point de la souris...

    <br />- (void)mouseDown:(NSEvent *)theEvent<br />{<br />    int nbElem, ix;<br />    NSPoint point[10], point1, curPoint;<br />    NSBezierPath *tmpPath;<br />    BOOL pointOK;<br />    float i, j;<br /><br />    nbElem=[path elementCount];<br />    curPoint=[self convertPoint:[theEvent locationInWindow] fromView:nil];<br /><br />    for (i=-2; i&lt;=2; i++)<br />    {<br />        for (j=-2; j&lt;=2; j++)<br />        {<br />            for (ix=0; ix&lt;nbElem; ix++)<br />            {<br />                pointOK=NO;<br />                switch ([path elementAtIndex:ix associatedPoints:point])<br />                {<br />                    case NSMoveToBezierPathElement:<br />                        point1=point[0];<br />                        break;<br /><br />                    case NSLineToBezierPathElement:<br />                        tmpPath=[[NSBezierPath alloc] init];<br />                        [tmpPath moveToPoint:point1];<br />                        [tmpPath lineToPoint:point[0]];<br />                        point1=point[0];<br />                        pointOK=[tmpPath containsPoint:NSMakePoint(curPoint.x+i, curPoint.y+j)];<br />                        [tmpPath release];<br />                        break;<br />                }<br />                if (pointOK) break;<br />            }<br />            if (pointOK) break;<br />        }<br />        if (pointOK) break;<br />    }<br /><br />    NSLog(@&quot;mon point est sur un bezier-path ? %i&quot;, pointOK);<br />}<br />
    


    .
  • UniXUniX Membre
    15:14 modifié #26
    Bru, j'ai fait le test sur les x points autour de la souris, voici mon code :
    int nbElem, ix;<br />		NSPoint point[10], point1, curPoint, tempPoint;<br />		NSBezierPath *tmpPath;<br />		BOOL pointOK;<br /><br />		nbElem=[path elementCount];<br />		curPoint=[self convertPoint:[event locationInWindow] fromView:nil];<br /><br />		for (ix=0; ix&lt;nbElem; ix++)<br />		{<br />			pointOK=NO;<br />			switch ([path elementAtIndex:ix associatedPoints:point])<br />			{<br />				case NSMoveToBezierPathElement:<br />				point1=point[0];<br />				break;<br /><br />				case NSLineToBezierPathElement:<br />				tmpPath=[[NSBezierPath alloc] init];<br />				[tmpPath moveToPoint:point1];<br />				[tmpPath lineToPoint:point[0]];<br />				//[tmpPath setFlatness:5.0];<br />				point1=point[0];<br />				float ih, iv;<br />				for (ih = -2.0; ih &lt; 3.0; ih++)<br />				{<br />					tempPoint.x = curPoint.x + ih;<br />					for (iv = -2.0; iv &lt; 3.0; iv++)<br />					{<br />						tempPoint.y = curPoint.y + iv;<br />						pointOK=[tmpPath containsPoint:tempPoint];<br />						if (pointOK) break;<br />					}<br />					if (pointOK) break;<br />				}<br />				[tmpPath release];<br />				break;<br />			}<br />			if (pointOK) break;<br />		}<br />		if(pointOK == YES)<br />		{NSBeep();}<br />
    

    Le pb n'est pas là . Cette méthode ne fonctionne pas, car, visiblement, les coordonnées des points composant un NSBezierPath, ne sont pas des entiers ou des float sans décimales (voir mes posts précédants) ..... Donc containsPoint: ne renvoie jamais YES.

    Eddy58, je viens de jeter un oeil rapide à  ton lien, ça peut être intéressant. Je vais le regarder de plus près.
  • BruBru Membre
    15:14 modifié #27
    Et chez moi ça marche...



    [Fichier joint supprimé par l'administrateur]
  • UniXUniX Membre
    octobre 2005 modifié #28
    Ben je sais pas quoi dire  ???

    Je pense que tu as un cas particulier (ou c'est moi qui en ai un) avec peut être des coordonnées de points entiers. Moi, en fait, j'ai un coef de zoom appliqué à  ma vue, donc je dois avoir des coordonnées décimales, et dans ce cas, ça ne fonctionne pas ... Je ne vois que cette explication.

    Essayes peut être la manip en mettant des coordonnées à  6 décimales pour les points de construction de ton bezier, pour voir.
  • UniXUniX Membre
    15:14 modifié #29
    L'exemple donné par Eddy58 marche impec  <3 <3 <3 <br />
    Merci à  vous de vous être penché sur le problème.
Connectez-vous ou Inscrivez-vous pour répondre.