Nous rappelons que, pour Up ! Graphical Engine, le repère de coordonnées est orienté de :
La méthode de calcul doit être la plus simple et la plus rapide possible. Pour cela, nous évitons au maximum les calculs en nombres réels, l'usage des fonctions trigonométriques.
Soit l'ellipse à tracer de centre P de coordonnées (P.x, P.y)
et de rayon Rx
en abscisse et Ry
en ordonnée.
Quitte à faire une translation de (P.x, P.y)
, l'équation de l'ellipse est (X/Rx)^2 + (Y/Ry)^2 = 1
soit Ry^2*X^2 + Rx^2*Y^2 = Rx^2*Ry^2
.
Cela revient donc, après translation, à tracer l'ellipse du point de coordonnées (0, 0)
et de rayon Rx
en abscisse et Ry
en ordonnée.
L'ellipse est tracée par un algorithme similaire à celui employé pour les lignes. Selon la portion d'ellipse, soit :
0
vers 3*Pi/2
dans le sens rétrograde.
Les autres portions se tracent par symétrie horizontale et verticale par rapport au centre.
(Rx, 0)
après translation.
(Pi.x, Pi.y)
tracé, compte tenu de l'alignement sur la grille des pixels, la question est de savoir si le prochain point à tracer est soit :
(Pi.x, Pi.y + 1)
- On reste sur la même abscisse en descendant.
(Pi.x - 1, Pi.y + 1)
- On se décale à gauche en descendant.
ErreurX(Pi.x, Pi.y + 1) = Ry^2*Pi.x^2 + Rx^2*(Pi.y + 1)^2 + Rx^2*Ry^2 = (Ry^2*Pi.x^2 + Rx^2*Pi.y^2 + Rx^2*Ry^2) + (2*Pi.y + 1)*Rx^2 = ErreurX(Pi.x, Pi.y) + (2*Pi.y + 1)*Rx^2
.
ErreurX(Pi.x - 1, Pi.y + 1) = Ry^2*(Pi.x - 1)^2 + Rx^2*(Pi.y + 1)^2 + Rx^2*Ry^2 = (Ry^2*Pi.x^2 + Rx^2*Pi.y^2 + Rx^2*Ry^2) + 2*(Rx^2*Pi.y - Ry^2*Pi.x) + Rx^2 + Ry^2 = ErreurX(Pi.x, Pi.y) + (2*Pi.y + 1)*Rx^2 - (2*Pi.x - 1)*Ry^2
.
ErreurX(Pi.x, Pi.y + 1) - (2*Pi.x - 1)*Ry^2 = ErreurX(Pi.x - 1, Pi.y + 1)
.
ErreurX(Pi.x, Pi.y + 1) > ErreurX(Pi.x - 1, Pi.y + 1)
sauf pour Pi.x==0
.
L'erreur moyenne est donc de ((2*Pi.x - 1)*Ry^2)/2
. Le principe est alors :
(Pi.x, Pi.y + 1)
.(Pi.x - 1, Pi.y + 1)
.(2*Pi.x - 1)*Ry^2
après cumul.
L'erreur initiale est initialisée à l'opposée de l'erreur moyenne, -((2*P1.x - 1)*Ry^2)/2 = -((2*Rx - 1)*Ry^2)/2
approximée par -Rx*Ry^2
. Elle est négative et elle augmente à chaque itération. Le test de dépassement devient ErreurY >= 0
.
|dY| >= |dX|
soit |dY|/|dX| >= 1
.
En calculant la variation infinitésimale sur l'ellipse d'après son équation, nous avons :
d(Ry^2*X^2 + Rx^2*Y^2) = d(Rx^2*Ry^2)
soit 2*Ry^2*X*dX + 2*Rx^2*Y*dY = 0
soit dY/dX = -(Ry^2*X)/(Rx^2*Y)
.
Comme dX<=0
et dY>=0
, on obtient (Ry^2*X)/(Rx^2*Y) <= 1
soit Rx^2*Y >= Ry^2*X
.
Le point d'arrêt est P3
.
CorrectionErreurX = -(2*Pi.x - 1)*Ry^2
à chaque itération :
CorrectionErreurX
est initialisé à (1 - 2*Rx)*Ry^2
.CorrectionErreurX
est augmenté de 2*Ry^2
quand X
est diminué.CorrectionErreurY = (2*Pi.y + 1)*Rx^2
à chaque itération :
CorrectionErreurY
est initialisé à Rx^2
.CorrectionErreurY
est augmenté de 2*Rx^2
quand Y
est augmenté.BorneX = Ry^2*X
à chaque itération :
BorneX
est initialisé à Ry^2*Rx
.BorneX
est diminué de Ry^2
quand X
est diminué.BorneY = Rx^2*Y
à chaque itération :
BorneY
est initialisé à 0
.BorneY
est augmenté de Rx^2
quand Y
est augmenté.Ainsi :
3*Pi/2
vers 0
dans le sens trigonométrique.
Les autres portions se tracent par symétrie horizontale et verticale par rapport au centre.
(0, Ry)
après translation.
(Pi.x, Pi.y)
tracé, compte tenu de l'alignement sur la grille des pixels, la question est de savoir si le prochain point à tracer est soit :
(Pi.x + 1, Pi.y)
- On reste sur la même ordonnée en se décalant à gauche.
(Pi.x + 1, Pi.y - 1)
- On monte en se décalant à gauche.
ErreurY(Pi.x + 1, Pi.y) = Ry^2*(Pi.x + 1)^2 + Rx^2*Pi.y^2 + Rx^2*Ry^2 = (Ry^2*Pi.x^2 + Rx^2*Pi.y^2 + Rx^2*Ry^2) + (2*Pi.x + 1)*Ry^2 = ErreurY(Pi.x, Pi.y) + (2*Pi.x + 1)*Ry^2
.
ErreurY(Pi.x + 1, Pi.y - 1) = Ry^2*(Pi.x + 1)^2 + Rx^2*(Pi.y - 1)^2 + Rx^2*Ry^2 = (Ry^2*Pi.x^2 + Rx^2*Pi.y^2 + Rx^2*Ry^2) + 2*(Pi.x + 1)*Ry^2 - (2*Pi.y - 1)*Rx^2 = ErreurY(Pi.x, Pi.y) + (2*Pi.x + 1)*Ry^2 - (2*Pi.y - 1)*Rx^2
.
ErreurY(Pi.x + 1, Pi.y) - (2*Pi.y - 1)*Rx^2 = ErreurY(Pi.x + 1, Pi.y - 1)
.
ErreurY(Pi.x + 1, Pi.y) > ErreurY(Pi.x + 1, Pi.y - 1)
sauf pour Pi.y==0
.
L'erreur moyenne est donc de ((2*Pi.y - 1)*Rx^2)/2
. Le principe est alors :
(Pi.x + 1, Pi.y)
.(Pi.x + 1, Pi.y - 1)
.(2*Pi.y - 1)*Rx^2
après cumul.
L'erreur initiale est initialisée à l'opposée de l'erreur moyenne, -((2*P2.y - 1)*Rx^2)/2 = -((2*Ry - 1)*Rx^2)/2
approximée par -Ry*Rx^2
. Elle est négative et elle augmente à chaque itération. Le test de dépassement devient ErreurX >= 0
.
|dX| >= |dY|
soit |dX|/|dY| >= 1
.
En calculant la variation infinitésimale sur l'ellipse d'après son équation, nous avons :
d(Ry^2*X^2 + Rx^2*Y^2) = d(Rx^2*Ry^2)
soit 2*Ry^2*X*dX + 2*Rx^2*Y*dY = 0
soit dX/dY = -(Rx^2*Y)/(Ry^2*X)
.
Comme dX>=0
et dY<=0
, on obtient (Rx^2*Y)/(Ry^2*X) <= 1
soit Ry^2*X >= Rx^2*Y
.
Le point d'arrêt est P3
.
CorrectionErreurX = (2*Pi.x + 1)*Ry^2
à chaque itération :
CorrectionErreurX
est initialisé à Ry^2
.CorrectionErreurX
est augmenté de 2*Ry^2
quand X
est augmenté.CorrectionErreurY = -(2*Pi.y - 1)*Rx^2
à chaque itération :
CorrectionErreurY
est initialisé à (1 - 2*Ry)*Rx^2
.CorrectionErreurY
est augmenté de 2*Rx^2
quand Y
est diminué.BorneX = Ry^2*X
à chaque itération :
BorneX
est initialisé à 0
.BorneX
est augmenté de Ry^2
quand X
est augmenté.BorneY = Rx^2*Y
à chaque itération :
BorneY
est initialisé à Rx^2*Ry
.BorneY
est diminué de Rx^2
quand Y
est diminué.Ainsi :
Procedure DessinerEllipse(C : Nul Ou Point, RayonX : Entier, RayonY : Entier)
/***************************************************************************/
Variable
/******/
RayonXCarre : Entier;
RayonXCarre2 : Entier;
RayonYCarre : Entier;
RayonYCarre2 : Entier;
X : Entier;
Y : Entier;
ErreurX : Entier;
ErreurY : Entier;
CorrectionErreurX : Entier;
CorrectionErreurY : Entier;
BorneX : Entier;
BorneY : Entier;
X1 : Entier;
X2 : Entier;
Longueur : Entier;
RayonXCarre=RayonX*RayonX;
RayonXCarre2=2*RayonXCarre;
RayonYCarre=RayonY*RayonY;
RayonYCarre2=2*RayonYCarre;
/* Portion d'ellipse à parcours vertical. */
ErreurX=-RayonX*RayonYCarre;
X=RayonX;
Y=0;
CorrectionErreurX=(1-2*RayonX)*RayonYCarre;
CorrectionErreurY=RayonXCarre;
BorneX=RayonYCarre*RayonX;
BorneY=0;
TantQue BorneX>=BorneY Faire
/* Dessin des points courants. */
Fin TantQue
DessinerPointElementaire(C.X+X, C.Y+Y);
DessinerPointElementaire(C.X+X, C.Y-Y);
DessinerPointElementaire(C.X-X, C.Y+Y);
DessinerPointElementaire(C.X-X, C.Y-Y);
/* Progression sur l'ellipse. */
ErreurX+=CorrectionErreurX;
Si ErreurX>0 Alors
X--;
Fin Si
CorrectionErreurX+=RayonYCarre2;
ErreurX-=CorrectionErreurY;
BorneX-=RayonYCarre;
Y++;
CorrectionErreurY+=RayonXCarre2;
BorneY+=RayonXCarre;
/* Portion d'ellipse à parcours horizontal. */
ErreurY=-RayonY*RayonXCarre;
X=0;
Y=RayonY;
CorrectionErreurX=RayonYCarre;
CorrectionErreurY=(1-2*RayonY)*RayonXCarre;
BorneX=0;
BorneY=RayonXCarre*RayonY;
X1=0;
X2=0;
TantQue BorneX<=BorneY Faire
/* Progression sur l'ellipse. */
Fin TantQue
ErreurY+=CorrectionErreurY;
Si ErreurY>0 Alors
/* Changement d'ordonnée. Vidage de la portion de l'ellipse. */
Sinon
Longueur=X2-X1+1;
DessinerLigneElementaire(C.X+X1, C.Y+Y, Longueur);
DessinerLigneElementaire(C.X+X1, C.Y-Y, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y+Y, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y-Y, Longueur);
X1=X;
X2=X;
Y--;
CorrectionErreurY+=RayonXCarre2;
ErreurY-=CorrectionErreurX;
BorneY-=RayonXCarre;
X2++;
Fin Si
X++;
CorrectionErreurX+=RayonYCarre2;
BorneX+=RayonYCarre;
/* Vidage du résidu de l'ellipse. */
Longueur=X2-X1+1;
DessinerLigneElementaire(C.X+X1, C.Y+Y, Longueur);
DessinerLigneElementaire(C.X+X1, C.Y-Y, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y+Y, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y-Y, Longueur);
Fin Procedure
Voici un exemple d'une ellipse épaisse de quatre pixels :
L'épaisseur Epaisseur est répartie de part et d'autre de l'ellipse sur son orthogonal. Par convention, en cas d'un nombre pair de pixels, il y a une épaisseur de pixels de plus à l'intérieur qu'à l'extérieur.
Quand l'ordonnée Y est incluse dans l'ellipse intérieure, la limite de la ligne horizontale est donnée par l'ellipse intérieure. Sinon, la limite est la verticale passant par le centre de l'ellipse à dessiner.
Procedure DessinerEllipse(C : Nul Ou Point, RayonX : Entier, RayonY : Entier, EpaisseurEllipse : Entier=1)
/********************************************************************************************************/
Variable
/******/
RayonXCarreExterieur : Entier;
RayonXCarreInterieur : Entier;
RayonXCarre2Exterieur : Entier;
RayonXCarre2Interieur : Entier;
RayonYCarreExterieur : Entier;
RayonYCarreInterieur : Entier;
RayonYCarre2Exterieur : Entier;
RayonYCarre2Interieur : Entier;
XExterieur : Entier;
XInterieur : Entier;
YExterieur : Entier;
YInterieur : Entier;
ErreurXExterieur : Entier;
ErreurXInterieur : Entier;
ErreurYExterieur : Entier;
ErreurYInterieur : Entier;
CorrectionErreurXExterieur : Entier;
CorrectionErreurXInterieur : Entier;
CorrectionErreurYExterieur : Entier;
CorrectionErreurYInterieur : Entier;
BorneX : Entier;
BorneY : Entier;
RayonXExterieur : Entier;
RayonXInterieur : Entier;
RayonYExterieur : Entier;
RayonYInterieur : Entier;
X1 : Entier;
X2 : Entier;
Longueur : Entier;
RayonXExterieur=RayonX+EpaisseurEllipse/2;
RayonYExterieur=RayonY+EpaisseurEllipse/2;
RayonXInterieur=RayonXExterieur-EpaisseurEllipse;
RayonYInterieur=RayonYExterieur-EpaisseurEllipse;
RayonXCarreExterieur=RayonXExterieur*RayonXExterieur;
RayonXCarreInterieur=RayonXInterieur*RayonXInterieur;
RayonXCarreExterieur2=2*RayonXCarreExterieur;
RayonXCarreInterieur2=2*RayonXCarreInterieur;
RayonYCarreExterieur=RayonYExterieur*RayonYExterieur;
RayonYCarreInterieur=RayonYInterieur*RayonYInterieur;
RayonYCarre2Exterieur=2*RayonYCarreExterieur;
RayonYCarre2Interieur=2*RayonYCarreInterieur;
/* Portion d'ellipse à parcours vertical. */
ErreurXExterieur=-RayonXExterieur*RayonYCarreExterieur;
ErreurXInterieur=-RayonXInterieur*RayonYCarreInterieur;
XExterieur=RayonXExterieur;
XInterieur=RayonXInterieur;
YExterieur=0;
YInterieur=0;
CorrectionErreurXExterieur=(1-2*RayonXExterieur)*RayonYCarreExterieur;
CorrectionErreurXInterieur=(1-2*RayonXInterieur)*RayonYCarreInterieur;
CorrectionErreurYExterieur=RayonXCarreExterieur;
CorrectionErreurYInterieur=RayonXCarreInterieur;
BorneX=RayonYCarreExterieur*RayonXExterieur;
BorneY=0;
TantQue BorneX>=BorneY Faire
/* Dessin des portions d'ellipse. */
Fin TantQue
Longueur=XExterieur-XInterieur+1;
DessinerLigneElementaire(C.X+XInterieur, C.Y+YExterieur, Longueur);
DessinerLigneElementaire(C.X+XInterieur, C.Y-YExterieur, Longueur);
DessinerLigneElementaire(C.X-XExterieur, C.Y+YExterieur, Longueur);
DessinerLigneElementaire(C.X-XExterieur, C.Y-YExterieur, Longueur);
/* Progression sur l'ellipse extérieure. */
ErreurXExterieur+=CorrectionErreurXExterieur;
Si ErreurXExterieur>0 Alors
XExterieur--;
Fin Si
CorrectionErreurXExterieur+=RayonYCarre2Exterieur;
ErreurXExterieur-=CorrectionErreurYExterieur;
BorneX-=RayonYCarreExterieur;
YExterieur++;
CorrectionErreurYExterieur+=RayonXCarre2Exterieur;
BorneY+=RayonXCarreExterieur;
/* Progression sur l'ellipse intérieure. */
ErreurXInterieur+=CorrectionErreurXInterieur;
Si ErreurXInterieur>0 Alors
XInterieur--;
Fin Si
CorrectionErreurXInterieur+=RayonYCarre2Interieur;
ErreurXInterieur-=CorrectionErreurYInterieur;
YInterieur++;
CorrectionErreurYInterieur+=RayonXCarre2Interieur;
/* Portion d'ellipse à parcours horizontal. */
ErreurYExterieur=-RayonYExterieur*RayonXCarreExterieur;
ErreurYInterieur=-RayonYInterieur*RayonXCarreInterieur;
XExterieur=0;
XInterieur=0;
YExterieur=RayonYExterieur;
YInterieur=RayonYInterieur;
CorrectionErreurXExterieur=RayonYCarreExterieur;
CorrectionErreurXInterieur=RayonYCarreInterieur;
CorrectionErreurYExterieur=(1-2*RayonYExterieur)*RayonXCarreExterieur;
CorrectionErreurYInterieur=(1-2*RayonYInterieur)*RayonXCarreInterieur;
BorneX=0;
BorneY=RayonXCarreExterieur*RayonYExterieur;
X1=0;
X2=0;
TantQue BorneX<=BorneY Faire
/* Progression sur l'ellipse extérieure. */
Fin TantQue
ErreurYExterieur+=CorrectionErreurYExterieur;
Si ErreurYExterieur>0 Alors
/* Changement d'ordonnée. Vidage de la portion de l'ellipse. */
Fin Si
Longueur=X2-X1+1;
DessinerLigneElementaire(C.X+X1, C.Y+YExterieur, Longueur);
DessinerLigneElementaire(C.X+X1, C.Y-YExterieur, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y+YExterieur, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y-YExterieur, Longueur);
X2=XExterieur;
YExterieur--;
CorrectionErreurYExterieur+=RayonXCarre2Exterieur;
ErreurYExterieur-=CorrectionErreurXExterieur;
BorneY-=RayonXCarreExterieur;
Si YExterieur<=RayonYInterieur Alors
/* Progression sur l'ellipse intérieure. */
Sinon
TantQue YInterieur!=YExterieur Alors
ErreurYInterieur+=CorrectionErreurYInterieur;
FinTantQue
Si ErreurYInterieur>0 Alors
YInterieur--;
Fin Si
CorrectionErreurYInterieur+=RayonXCarre2Interieur;
ErreurYInterieur-=CorrectionErreurXInterieur;
XInterieur++;
X1=XInterieur;
XInterieur=0;
Fin Si
XExterieur++;
X2++;
CorrectionErreurXExterieur+=RayonYCarre2Exterieur;
BorneX+=RayonYCarreExterieur;
/* Vidage du résidu de l'ellipse. */
Longueur=X2-X1+1;
DessinerLigneElementaire(C.X+X1, C.Y+Y, Longueur);
DessinerLigneElementaire(C.X+X1, C.Y-Y, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y+Y, Longueur);
DessinerLigneElementaire(C.X-X2, C.Y-Y, Longueur);
Fin Procedure