Cercles et ellipses

A propos du code

Le code dans cet article a été écrit avec Code::Blocks et la SDL 2.
Vous pouvez trouver ici un guide pour installer ces logiciels.
Bien qu'il soit basé sur la SDL, je n'utilise pas ses fonctions directement. J'ai écrit une petite bibliothèque
avec quelques fonctions basiques pour faciliter la compréhension et la portabilité dans un autre langage.
Vous pouvez en apprendre plus sur cette bibliothèque ici.

Cet article utilise la fonction de ligne qu'on a ajouté dans l'algorithme de ligne de Bresenham.

Cercle polaire

Quand vous dessinez un cercle à l'école, vous prenez votre compas, vous l'écartez au rayon voulu, vous placez la
pointe au centre, puis vous tournez le compas pour tracer le cercle.
On peut imaginer que le rayon qu'on choisit est un segment invisible d'une certaine longueur.
On place une extrémité du segment sur le centre, et on tourne l'autre suivant un angle allant de 0 à 360 degrés.

Pour simplifier on va dire que le centre est l'origine du plan. Cette image montre le début du tracé.


R et θ s'appellent les coordonnées polaires du point.
Mais si on veut dessiner le cercle à l'écran on va avoir besoin des coordonnées cartésiennes de ses points (x et
y).


Si l'on regarde bien, on peut voir un triangle rectangle.


Les formules de base de la trigonométrie nous donnent la valeur du sinus et du cosinus:
		cos θ = x / R
		sin θ = y / R
				
Donc, on peut facilement retrouver notre x et notre y:
		x = R * cos θ
		y = R * sin θ
				
Maintenant, on avait fait une simplification en disant que le centre de notre cercle est à l'origine.
Si on veut dessiner notre cercle autour d'un autre centre (xC, yC) il faut juste qu'on
ajoute ses coordonnées:
		x = xC + R * cos θ
		y = yC + R * sin θ
				
Ensuite, si on fait varier l'angle de 0 à 360 degrés on devrait obtenir tous les points de notre cercle.
Mais si on écrit un programme en C, les fonctions standard cos() et sin() ne prennent pas un angle en degrés, mais
en radians.
Alors on va utiliser une macro pour convertir les degrés en radians:
		#define DEG_TO_RAD(_x)  ((_x) * M_PI / 180.0)
				
Maintenant on peut dessiner notre cercle:
		#include <stdio.h>
		#include <stdlib.h>
		#include <math.h>
		#include "main.h"
		#include "Graphics.h"
		#include "System.h"

		#define SCREEN_WIDTH    640
		#define SCREEN_HEIGHT   480
		#define DEG_TO_RAD(_x)  ((_x) * M_PI / 180.0)

		int main(int argc, char* argv[])
		{
			// init the window
			gfx.init("Circle", SCREEN_WIDTH, SCREEN_HEIGHT);
			gfx.init2D();
			gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));

			int centerX = SCREEN_WIDTH / 2;
			int centerY = SCREEN_HEIGHT / 2;
			int radius = 200;

			for (int i = 0; i < 360; i++)
			{
				float angle = DEG_TO_RAD(i);
				int x = centerX + radius * cos(angle);
				int y = centerY + radius * sin(angle);

				gfx.setPixel(x, y, Color(255, 255, 255));

				gfx.render();
				sys.wait(20);
			}

			// wait until we quit
			while (sys.isQuitRequested() == false)
			{
				gfx.render();
				sys.wait(20);
				sys.processEvents();
			}

			gfx.quit();

			return EXIT_SUCCESS;
		}
		
		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
J'ai ajouté une petite temporisation entre chaque point.
Vous devriez voir le cercle apparaître dans le sens des aiguille d'une montre parce que les y augmentent vers le bas
à l'écran.


Boucher les trous

Seulement il manque des pixels dans ce cercle.
C'est encore plus visible si on essaye de le remplir en traçant des lignes à partir de son centre en remplaçant
		gfx.setPixel(x, y, Color(255, 255, 255));
				
par
		gfx.line(centerX, centerY, x, y, Color(255, 255, 255));
		
		Télécharger le code source
		Télécharger l'exécutable pour Windows
				


Mais pour l'instant on va rester avec des cercles vides.
Une façon de corriger ça serait d'augmenter le nombre d'étapes qu'on fait pour aller de 0 à 360 degrés en
augmentant l'angle par exemple de 0.5 degrés à chaque fois.
Mais on a une méthode plus simple pour avoir un cercle continu même s'il n'est pas parfait.
Comme on a une fonction de tracé de ligne, on va tracer des lignes entre les points donnés par 2 valeurs
successives de l'angle.
		for (int i = 0; i < 360; i++)
		{
			float angle0 = DEG_TO_RAD(i);
			int x0 = centerX + radius * cos(angle0);
			int y0 = centerY + radius * sin(angle0);

			float angle1 = DEG_TO_RAD(i + 1);
			int x1 = centerX + radius * cos(angle1);
			int y1 = centerY + radius * sin(angle1);

			gfx.line(x0, y0, x1, y1, Color(255, 255, 255));

			gfx.render();
			sys.wait(20);
			sys.processEvents();
		}
	
		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
Et on obtient un cercle plus beau.


Changer les paramètres

Maintenant on va essayer de changer quelques paramètres des équations qu'on a trouvées pour voir ce qui se passe.
Pour mémoire, on avait:
		x = xC + R * cos θ
		y = yC + R * sin θ
				
Modifier xC et yC changera le centre du cercle.
Il n'y a rien de très intéressant à faire avec ça.
Mais qu'est-ce qu'on obtient si on met des rayons différents en x et en y ?
		int centerX = SCREEN_WIDTH / 2;
		int centerY = SCREEN_HEIGHT / 2;
		int radiusX = 200;
		int radiusY = 100;

		for (int i = 0; i < 360; i++)
		{
			float angle0 = DEG_TO_RAD(i);
			int x0 = centerX + radiusX * cos(angle0);
			int y0 = centerY + radiusY * sin(angle0);

			float angle1 = DEG_TO_RAD(i + 1);
			int x1 = centerX + radiusX * cos(angle1);
			int y1 = centerY + radiusY * sin(angle1);

			gfx.line(x0, y0, x1, y1, Color(255, 255, 255));

			gfx.render();
			sys.wait(20);
		}
	
		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
On obtient une ellipse.


C'était facile à prévoir parce que si un des rayons n'est pas le même que l'autre, on peut penser qu'on l'a
multiplié par une constante, donc on aurait "étiré" notre cercle d'origine le long d'un axe.

Maintenant qu'est-ce qu'il se passe si on ajoute une valeur à l'angle θ ?
Si on ajoute la même valeur sur les 2 axes, On aura toujours un cercle, mais le point où il commence à être tracé
sera différent.
Vous pouvez vérifier ça vous-même.
Mais si on ajoute une valeur à seulement un des axes On aura un résultat différent.
		int centerX = SCREEN_WIDTH / 2;
		int centerY = SCREEN_HEIGHT / 2;
		int radius = 200;

		float angleAdd = DEG_TO_RAD(20);

		for (int i = 0; i < 360; i++)
		{
			float angle0 = DEG_TO_RAD(i);
			int x0 = centerX + radius * cos(angle0 + angleAdd);
			int y0 = centerY + radius * sin(angle0);

			float angle1 = DEG_TO_RAD(i + 1);
			int x1 = centerX + radius * cos(angle1 + angleAdd);
			int y1 = centerY + radius * sin(angle1);

			gfx.line(x0, y0, x1, y1, Color(255, 255, 255));

			gfx.render();
			sys.wait(20);
		}
	
		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
Maintenant on a une ellipse penchée.


Si on fait varier la valeur qu'on ajoute entre -90 et 90 degrés on obtient une belle image qui ressemble presque à
de la 3D.
		for (int i = 0; i < 360; i++)
		{
			for (int j = -90; j <= 90; j += 10)
			{
				float angleAdd = DEG_TO_RAD(j);

				float angle0 = DEG_TO_RAD(i);
				int x0 = centerX + radius * cos(angle0 + angleAdd);
				int y0 = centerY + radius * sin(angle0);

				float angle1 = DEG_TO_RAD(i + 1);
				int x1 = centerX + radius * cos(angle1 + angleAdd);
				int y1 = centerY + radius * sin(angle1);

				gfx.line(x0, y0, x1, y1, Color(255, 255, 255));
			}

			gfx.render();
			sys.wait(20);
		}
	
		Télécharger le code source
		Télécharger l'exécutable pour Windows
				

On parlera d'une autre façon de changer ces formules dans un futur article.

Liens

Vidéo du dernier programme