Développement Mac et iPhone
Aujourd'hui nous allons permettre — enfin ! — de se déplacer dans l'ensemble de Mandelbrot.
Commencez par ouvrir MyDocument.xib et retirez l'instance de CFRMesurePerf qui ne nous est plus nécessaire.
L'utilisateur naviguera de la façon suivante:
Rappelez-vous que la classe CFRMandelbrotRender contient une variable d'instance centre:
Complexe_t centre; // Centre du repère
De fait, se déplacer dans l'ensemble consiste à déplacer le centre du repère, à recalculer la bitmap et à l'afficher.
Je vous rappelle que NSView hérite de NSResponder. Or, la méthode -[NSReponder mouseDragged:] est appelée lorsque la souris est glissée sur la vue, avec le bouton principal appuyé :
- (void)mouseDragged:(NSEvent *)theEvent
{
// Déplacer le centre de la vue
[render decalerCentreX:-[theEvent deltaX]
y:[theEvent deltaY]];
Je vais revenir tout de suite sur la méthode decalerCentreX:y:. Les méthodes -[NSEvent deltaX] et -[NSEvent deltaY] permettent d'obtenir le nombre de points dont s'est déplacé le pointeur de la souris pendant le glissé.
[self setNeedsDisplay:YES];
}
Cette méthode demande à la vue se réafficher dans la prochaine boucle d'affichage : la méthode -drawRect: sera appelée à nouveau.
Venons-en à la méthode -[CRFMandelbrotRender decalerCentreX:y:]. Nous avons ici une conversion d'échelle à faire. En effet, les décalages lui sont exprimés en pixels, et il nous faut les exprimer en coordonnées "repère":
- (void) decalerCentreX:(NSInteger)pixelsHoriz
y:(NSInteger)pixelsVerti
{
double facteurReperePixel = largeur / largeurBitmap;
D'abord, nous calculons le rapport d'échelle entre la largeur du repère, et celle de la bitmap (en pixels, comme la vue).
double deltaX = pixelsHoriz * facteurReperePixel;
double deltaY = pixelsVerti * facteurReperePixel;
Nous appliquons alors ce facteur aux décalages en pixels pour obtenir les décalages en coordonnées "repère".
centre.reel += deltaX;
centre.imag += deltaY;
}
Enfin, nous translatons le centre du repère.
Et voilà, lancez le programme, ça fonctionne.
Zoomer consiste à modifier la variable largeur de CFRMandelbrotRender, qui représente la largeur de l'intervalle de calcul:
double largeur;
Ainsi, si vous divisez la largeur par 2, vous zoomez de 200%; si vous multipliez la largeur par 2, vous dézoomez de 50%:
- (void) zoomerDuFacteur:(double)facteur
{
largeur = largeur * facteur;
// Corriger les valeurs extrêmes
if(largeur < 0.0001)
largeur = 0.0001;
else if(largeur > 8.0)
largeur = 8.0;
}
J'ai ajouté la correction des valeurs extrêmes après quelques essais . Les limites sont empiriques; d'ailleurs, nous changerons sans doute la limite basse un jour.
Quand la molette est actionnée, la méthode -[NSResponder scrollWheel:] est appelée:
- (void)scrollWheel:(NSEvent *)theEvent
{
// deltaY est > 0 quand la molette est tournée en avant.
// Il vaut +/- 0.1 pour un petit mouvement et +/- 10 pour un grand.
double deltaY = [theEvent deltaY];
Il m'a fallu faire quelques essais pour régler l'amplitude du zoom. J'ai mesuré que tourner lentement la molette vers l'avant donnait un deltaY aux alentours de 0,1, et la tourner vite, autour de 10.
deltaY est négatif quand on la molette est tournée vers l'arrière.
[render zoomerDuFacteur: 1.0 + deltaY/20.0];
La division par 20 de deltaY est là encore empirique. Par exemple, en tournant rapidement la molette vers l'arrière, vous obtenez un facteur de zoom de l'ordre de 1+ (-10)/20) = 0.5, soit un zoom x2.
[self setNeedsDisplay:YES];
}
Lancez le programme: ça zoome !
Modifions la méthode -mouseDragged:
- (void)mouseDragged:(NSEvent *)theEvent
{
// La touche Contrôle est-elle appuyée ?
if([theEvent modifierFlags] & NSControlKeyMask)
{
Il nous faut d'abord savoir si la touche Contrôle est appuyée pour distinguer les deux type de glissés.
// Zoomer/dézoomer
double deltaRelatif = 2.0 * ([theEvent deltaY] / [self bounds].size.height);
Par soucis d'ergonomie, ce qui nous intéresse, n'est pas le déplacement absolu de la souris, mais son déplacement relatif. D'où le calcul de deltaY/(hauteur de la vue). J'ai ensuite ajouté un facteur 2, déterminé — vous l'aurez deviné — de façon empirique.
[render zoomerDuFacteur: 1.0 + deltaRelatif];
}
else
{
// Déplacer le centre de la vue
[render decalerCentreX:-[theEvent deltaX] y:[theEvent deltaY]];
}
[self setNeedsDisplay:YES];
}
Le reste a déjà été expliqué.
Lancez le programme, et admirez le résultat. Personnellement, je trouve l'affichage encore un peu lent.
En zoomant loin en avant, on reconnaît enfin la structure "fractale" de l'ensemble (le même motif se répétant à des échelles différentes).
Cependant, les détails ne sont alors plus très présents. C'est parce que nous avons fixé le nombre d'itérations maximales trop bas. Heureusement, nous permettrons prochainement de modifier ce seuil.
À bientôt.
Le projet XCode complet à télécharger.
Renaud Pradenc
Céroce.com