Les interpolations avec Lazarus (3/3)

Les courbes de Bézier cubiques

Gilles Vasseur

- Titre : Les interpolations avec Lazarus (3/3)

- Auteur : Gilles Vasseur

- Parution : 30 juin 2018

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2018 Gilles Vasseur. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Les courbes de Bézier cubiques (cubic bezier)

- II-A. Un exemple interactif

- II-B. La production de listes de coordonnées

- II-C. Les courbes de Bézier en action

- III. Des classes pour les interpolations, fonctions d'easing et les courbes de Bézier cubiques

- IV. Une unité pour les nostalgiques

- V. Conclusion

I - Introduction

Les programmes de test sont présents dans le répertoire exemples accompagnant le tutoriel.

II - Les courbes de Bézier cubiques (cubic bezier)

II-A - Un exemple interactif

II-B - La production de listes de coordonnées

II-C - Les courbes de Bézier en action

III - Des classes pour les interpolations, fonctions d'easing et les courbes de Bézier cubiques

IV - Une unité pour les nostalgiques

[Exemple Interpolations 08]

Comme souvent en Pascal, il est possible de travailler avec des fonctions hors des classes. Nous fournissons donc une unité baptisée interpolationfunctions composée des fonctions étudiées, mais accessibles directement.

En voici l'interface :

unit interpolationfunctions;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils;

type
TInterpolationFunction = function(AStart, AEnd, ADuration, AStep: Single):
Single;

function InPower(AStart, AChange, ADuration, AStep: Single; APower: Integer): Single;
function OutPower(AStart, AChange, ADuration, AStep: Single; APower: Integer): Single;
function InOutPower(AStart, AChange, ADuration, AStep: Single; APower: Integer): Single;
function EaseNone(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInQuad(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutQuad(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutQuad(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInQuad(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInCubic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutCubic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutCubic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInCubic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInQuart(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutQuart(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutQuart(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInQuart(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInQuint(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutQuint(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutQuint(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInQuint(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInSine(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutSine(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutSine(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInSine(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInExpo(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutExpo(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutExpo(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInExpo(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInCirc(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutCirc(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutCirc(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInCirc(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInElastic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutElastic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutElastic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInElastic(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInBack(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutBack(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutBack(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInBack(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInBounce(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutBounce(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseInOutBounce(AStart, AEnd, ADuration, AStep: Single): Single;
function EaseOutInBounce(AStart, AEnd, ADuration, AStep: Single): Single;

Le type TInterpolationfunction permet d'utiliser ces fonctions comme paramètres de procédures ou d'autres fonctions.

Ainsi, l'entête de ComputeInterpolation pourra devenir :

function TMainForm.ComputeInterpolation(AInter: TInterpolationFunction; AStart,
AEnd, AStep: Single; ABack: Boolean): Single;

L'appel de cette fonction se fera en précisant qu'il ne s'agit pas d'exécuter la fonction fournie comme premier paramètre, mais d'accéder à son adresse. Nous utiliserons alors un @ devant son identificateur :

btnEaseInQuad.Left := ComputeInterpolationInt(@EaseInQuad, 0, LWidth, Li, LBack);

Pour le reste, la modification essentielle tient au fait que la durée n'est plus accessible directement puisque nous nous situons hors de la classe : il faut par conséquent prévoir un paramètre supplémentaire à toutes nos fonctions (Aduration).

Si nous reprenons l'exemple 03 donné dans le premier tutoriel pour afficher les fonctions d'easing, quelques modifications sont à apporter à sa réalisation. Voici le code source de l'unité principale modifiée :

unit main;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
StdCtrls, ComCtrls,
interpolationfunctions;

const
C_DefaultDuration = 100;
C_End = 200;

type

{ TMainForm }

TMainForm = class(TForm)
btnEaseOutInQuart: TButton;
btnLinear2: TButton;
btnEaseInQuad: TButton;
btnEaseOutCubic: TButton;
btnEaseInCubic: TButton;
btnEaseOutQuad: TButton;
btnEaseInOutCubic: TButton;
btnEaseInOutQuart: TButton;
btnEaseOutInCubic: TButton;
btnEaseInQuart: TButton;
btnEaseOutQuart: TButton;
btnEaseInQuint: TButton;
btnEaseOutQuint: TButton;
btnEaseOutInQuint: TButton;
btnEaseInOutQuint: TButton;
btnEaseInSine: TButton;
btnEaseOutSine: TButton;
btnEaseInOutSine: TButton;
btnEaseOutInSine: TButton;
btnEaseInOutQuad: TButton;
btnEaseOutInQuad: TButton;
btnEaseInExpo: TButton;
btnEaseInCirc: TButton;
btnEaseInElastic: TButton;
btnEaseInBack: TButton;
btnEaseInBounce: TButton;
btnEaseOutCirc: TButton;
btnEaseOutElastic: TButton;
btnEaseOutBack: TButton;
btnEaseOutBounce: TButton;
btnEaseOutExpo: TButton;
btnLinear: TButton;
btnEaseOutInCirc: TButton;
btnEaseOutInElastic: TButton;
btnEaseOutInBack: TButton;
btnEaseOutInBounce: TButton;
btnEaseInOutExpo: TButton;
btnEaseInOutCirc: TButton;
btnEaseInOutElastic: TButton;
btnEaseInOutBack: TButton;
btnEaseOutInExpo: TButton;
btnEaseInOutBounce: TButton;
pnlRight: TPanel;
pnlLeft: TPanel;
pnlBottom: TPanel;
pnlMain: TPanel;
Splitter1: TSplitter;
tbarWait: TTrackBar;
procedure btnEaseInOutQuadClick(Sender: TObject);
procedure btnEaseInQuadClick(Sender: TObject);
procedure btnEaseOutInQuadClick(Sender: TObject);
procedure btnEaseOutQuadClick(Sender: TObject);
procedure btnLinear2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure tbarWaitChange(Sender: TObject);
private
fDuration: Cardinal;
procedure SetDuration(AValue: Cardinal);
public
property Duration: Cardinal read fDuration write SetDuration default C_DefaultDuration;
function ComputeInterpolation(AInter: TInterpolationFunction;
AStart, AEnd, AStep: Single; ABack: Boolean = False):
Single;
function ComputeInterpolationInt(AInter: TInterpolationFunction; AStart, AEnd,
AStep: Integer; ABack: Boolean = False): Integer;
end;

var
MainForm: TMainForm;

implementation

{$R *.lfm}

{ TMainForm }

procedure TMainForm.btnLinear2Click(Sender: TObject);
// *** lancement des animations ***
var
Li, LWidth, LWidth2: Integer;
LBack: Boolean;
begin
tbarWait.Enabled := False;
LWidth := pnlLeft.Width - btnLinear2.Width;
LWidth2 := pnlRight.Width - btnLinear.Width;
for LBack := False to True do
for Li := 1 to Duration do
begin
btnLinear.Left := ComputeInterpolationInt(@EaseNone, 0, LWidth, Li, LBack);
btnLinear2.Left := ComputeInterpolationInt(@EaseNone, 0, LWidth2, Li, LBack);

btnEaseInQuad.Left := ComputeInterpolationInt(@EaseInQuad, 0, LWidth, Li, LBack);
btnEaseOutQuad.Left := ComputeInterpolationInt(@EaseOutQuad, 0, LWidth, Li, LBack);
btnEaseInOutQuad.Left := ComputeInterpolationInt(@EaseInOutQuad, 0, LWidth2, Li, LBack);
btnEaseOutInQuad.Left := ComputeInterpolationInt(@EaseOutInQuad, 0, LWidth2, Li, LBack);

btnEaseInCubic.Left := ComputeInterpolationInt(@EaseInCubic, 0, LWidth, Li, LBack);
btnEaseOutCubic.Left := ComputeInterpolationInt(@EaseOutCubic, 0, LWidth, Li, LBack);
btnEaseInOutCubic.Left := ComputeInterpolationInt(@EaseInOutCubic, 0, LWidth2, Li, LBack);
btnEaseOutInCubic.Left := ComputeInterpolationInt(@EaseOutInCubic, 0, LWidth2, Li, LBack);

btnEaseInQuart.Left := ComputeInterpolationInt(@EaseInQuart, 0, LWidth, Li, LBack);
btnEaseOutQuart.Left := ComputeInterpolationInt(@EaseOutQuart, 0, LWidth, Li, LBack);
btnEaseInOutQuart.Left := ComputeInterpolationInt(@EaseInOutQuart, 0, LWidth2, Li, LBack);
btnEaseOutInQuart.Left := ComputeInterpolationInt(@EaseOutInQuart, 0, LWidth2, Li, LBack);

btnEaseInQuint.Left := ComputeInterpolationInt(@EaseInQuint, 0, LWidth, Li, LBack);
btnEaseOutQuint.Left := ComputeInterpolationInt(@EaseOutQuint, 0, LWidth, Li, LBack);
btnEaseInOutQuint.Left := ComputeInterpolationInt(@EaseInOutQuint, 0, LWidth2, Li, LBack);
btnEaseOutInQuint.Left := ComputeInterpolationInt(@EaseOutInQuint, 0, LWidth2, Li, LBack);

btnEaseInSine.Left := ComputeInterpolationInt(@EaseInSine, 0, LWidth, Li, LBack);
btnEaseOutSine.Left := ComputeInterpolationInt(@EaseOutSine, 0, LWidth, Li, LBack);
btnEaseInOutSine.Left := ComputeInterpolationInt(@EaseInOutSine, 0, LWidth2, Li, LBack);
btnEaseOutInSine.Left := ComputeInterpolationInt(@EaseOutInSine, 0, LWidth2, Li, LBack);

btnEaseInExpo.Left := ComputeInterpolationInt(@EaseInExpo, 0, LWidth, Li, LBack);
btnEaseOutExpo.Left := ComputeInterpolationInt(@EaseOutExpo, 0, LWidth, Li, LBack);
btnEaseInOutExpo.Left := ComputeInterpolationInt(@EaseInOutExpo, 0, LWidth2, Li, LBack);
btnEaseOutInExpo.Left := ComputeInterpolationInt(@EaseOutInExpo, 0, LWidth2, Li, LBack);

btnEaseInCirc.Left := ComputeInterpolationInt(@EaseInCirc, 0, LWidth, Li, LBack);
btnEaseOutCirc.Left := ComputeInterpolationInt(@EaseOutCirc, 0, LWidth, Li, LBack);
btnEaseInOutCirc.Left := ComputeInterpolationInt(@EaseInOutCirc, 0, LWidth2, Li, LBack);
btnEaseOutInCirc.Left := ComputeInterpolationInt(@EaseOutInCirc, 0, LWidth2, Li, LBack);

btnEaseInElastic.Left := ComputeInterpolationInt(@EaseInElastic, 0, LWidth, Li, LBack);
btnEaseOutElastic.Left := ComputeInterpolationInt(@EaseOutElastic, 0, LWidth, Li, LBack);
btnEaseInOutElastic.Left := ComputeInterpolationInt(@EaseInOutElastic, 0, LWidth2, Li, LBack);
btnEaseOutInElastic.Left := ComputeInterpolationInt(@EaseOutInElastic, 0, LWidth2, Li, LBack);

btnEaseInBack.Left := ComputeInterpolationInt(@EaseInBack, 0, LWidth, Li, LBack);
btnEaseOutBack.Left := ComputeInterpolationInt(@EaseOutBack, 0, LWidth, Li, LBack);
btnEaseInOutBack.Left := ComputeInterpolationInt(@EaseInOutBack, 0, LWidth2, Li, LBack);
btnEaseOutInBack.Left := ComputeInterpolationInt(@EaseOutInBack, 0, LWidth2, Li, LBack);

btnEaseInBounce.Left := ComputeInterpolationInt(@EaseInBounce, 0, LWidth, Li, LBack);
btnEaseOutBounce.Left := ComputeInterpolationInt(@EaseOutBounce, 0, LWidth, Li, LBack);
btnEaseInOutBounce.Left := ComputeInterpolationInt(@EaseInOutBounce, 0, LWidth2, Li, LBack);
btnEaseOutInBounce.Left := ComputeInterpolationInt(@EaseOutInBounce, 0, LWidth2, Li, LBack);

sleep(10);
Repaint;
Application.ProcessMessages;
end;
tbarWait.Enabled := True;
end;

procedure TMainForm.btnEaseInQuadClick(Sender: TObject);
// *** fonctions IN ***
var
Li, LWidth: Integer;
LBack: Boolean;
begin
tbarWait.Enabled := False;
LWidth := pnlLeft.Width - btnLinear2.Width;
for LBack := False to True do
for Li := 1 to fDuration do
begin
btnLinear.Left := ComputeInterpolationInt(@EaseNone, 0, LWidth, Li, LBack);
btnEaseInQuad.Left := ComputeInterpolationInt(@EaseInQuad, 0, LWidth, Li, LBack);
btnEaseInCubic.Left := ComputeInterpolationInt(@EaseInCubic, 0, LWidth, Li, LBack);
btnEaseInQuart.Left := ComputeInterpolationInt(@EaseInQuart, 0, LWidth, Li, LBack);
btnEaseInQuint.Left := ComputeInterpolationInt(@EaseInQuint, 0, LWidth, Li, LBack);
btnEaseInSine.Left := ComputeInterpolationInt(@EaseInSine, 0, LWidth, Li, LBack);
btnEaseInExpo.Left := ComputeInterpolationInt(@EaseInExpo, 0, LWidth, Li, LBack);
btnEaseInCirc.Left := ComputeInterpolationInt(@EaseInCirc, 0, LWidth, Li, LBack);
btnEaseInElastic.Left := ComputeInterpolationInt(@EaseInElastic, 0, LWidth, Li, LBack);
btnEaseInBack.Left := ComputeInterpolationInt(@EaseInBack, 0, LWidth, Li, LBack);
btnEaseInBounce.Left := ComputeInterpolationInt(@EaseInBounce, 0, LWidth, Li, LBack);
sleep(10);
pnlLeft.Repaint;
Application.ProcessMessages;
end;
tbarWait.Enabled := True;
end;

procedure TMainForm.btnEaseInOutQuadClick(Sender: TObject);
// *** fonctions INOUT ***
var
Li, LWidth2: Integer;
LBack: Boolean;
begin
tbarWait.Enabled := False;
LWidth2 := pnlRight.Width - btnLinear.Width;
for LBack := False to True do
for Li := 1 to fDuration do
begin
btnLinear2.Left := ComputeInterpolationInt(@EaseNone, 0, LWidth2, Li, LBack);
btnEaseInOutQuad.Left := ComputeInterpolationInt(@EaseInOutQuad, 0, LWidth2, Li, LBack);
btnEaseInOutCubic.Left := ComputeInterpolationInt(@EaseInOutCubic, 0, LWidth2, Li, LBack);
btnEaseInOutQuart.Left := ComputeInterpolationInt(@EaseInOutQuart, 0, LWidth2, Li, LBack);
btnEaseInOutQuint.Left := ComputeInterpolationInt(@EaseInOutQuint, 0, LWidth2, Li, LBack);
btnEaseInOutSine.Left := ComputeInterpolationInt(@EaseInOutSine, 0, LWidth2, Li, LBack);
btnEaseInOutExpo.Left := ComputeInterpolationInt(@EaseInOutExpo, 0, LWidth2, Li, LBack);
btnEaseInOutCirc.Left := ComputeInterpolationInt(@EaseInOutCirc, 0, LWidth2, Li, LBack);
btnEaseInOutElastic.Left := ComputeInterpolationInt(@EaseInOutElastic, 0, LWidth2, Li, LBack);
btnEaseInOutBack.Left := ComputeInterpolationInt(@EaseInOutBack, 0, LWidth2, Li, LBack);
btnEaseInOutBounce.Left := ComputeInterpolationInt(@EaseInOutBounce, 0, LWidth2, Li, LBack);
sleep(10);
pnlRight.Repaint;
Application.ProcessMessages;
end;
tbarWait.Enabled := True;
end;

procedure TMainForm.btnEaseOutInQuadClick(Sender: TObject);
// *** fonctions OUTIN ***
var
Li, LWidth2: Integer;
LBack: Boolean;
begin
tbarWait.Enabled := False;
LWidth2 := pnlRight.Width - btnLinear.Width;
for LBack := False to True do
for Li := 1 to fDuration do
begin
btnLinear2.Left := ComputeInterpolationInt(@EaseNone, 0, LWidth2, Li, LBack);
btnEaseOutInQuad.Left := ComputeInterpolationInt(@EaseOutInQuad, 0, LWidth2, Li, LBack);
btnEaseOutInCubic.Left := ComputeInterpolationInt(@EaseOutInCubic, 0, LWidth2, Li, LBack);
btnEaseOutInQuart.Left := ComputeInterpolationInt(@EaseOutInQuart, 0, LWidth2, Li, LBack);
btnEaseOutInQuint.Left := ComputeInterpolationInt(@EaseOutInQuint, 0, LWidth2, Li, LBack);
btnEaseOutInSine.Left := ComputeInterpolationInt(@EaseOutInSine, 0, LWidth2, Li, LBack);
btnEaseOutInExpo.Left := ComputeInterpolationInt(@EaseOutInExpo, 0, LWidth2, Li, LBack);
btnEaseOutInCirc.Left := ComputeInterpolationInt(@EaseOutInCirc, 0, LWidth2, Li, LBack);
btnEaseOutInElastic.Left := ComputeInterpolationInt(@EaseOutInElastic, 0, LWidth2, Li, LBack);
btnEaseOutInBack.Left := ComputeInterpolationInt(@EaseOutInBack, 0, LWidth2, Li, LBack);
btnEaseOutInBounce.Left := ComputeInterpolationInt(@EaseOutInBounce, 0, LWidth2, Li, LBack);
sleep(10);
pnlRight.Repaint;
Application.ProcessMessages;
end;
tbarWait.Enabled := True;
end;

procedure TMainForm.btnEaseOutQuadClick(Sender: TObject);
// *** fonctions OUT ***
var
Li, LWidth: Integer;
LBack: Boolean;
begin
tbarWait.Enabled := False;
LWidth := pnlLeft.Width - btnLinear2.Width;
for LBack := False to True do
for Li := 1 to fDuration do
begin
btnLinear.Left := ComputeInterpolationInt(@EaseNone, 0, LWidth, Li, LBack);

btnEaseOutQuad.Left := ComputeInterpolationInt(@EaseOutQuad, 0, LWidth, Li, LBack);
btnEaseOutCubic.Left := ComputeInterpolationInt(@EaseOutCubic, 0, LWidth, Li, LBack);
btnEaseOutQuart.Left := ComputeInterpolationInt(@EaseOutQuart, 0, LWidth, Li, LBack);
btnEaseOutQuint.Left := ComputeInterpolationInt(@EaseOutQuint, 0, LWidth, Li, LBack);
btnEaseOutSine.Left := ComputeInterpolationInt(@EaseOutSine, 0, LWidth, Li, LBack);
btnEaseOutExpo.Left := ComputeInterpolationInt(@EaseOutExpo, 0, LWidth, Li, LBack);
btnEaseOutCirc.Left := ComputeInterpolationInt(@EaseOutCirc, 0, LWidth, Li, LBack);
btnEaseOutElastic.Left := ComputeInterpolationInt(@EaseOutElastic, 0, LWidth, Li, LBack);
btnEaseOutBack.Left := ComputeInterpolationInt(@EaseOutBack, 0, LWidth, Li, LBack);
btnEaseOutBounce.Left := ComputeInterpolationInt(@EaseOutBounce, 0, LWidth, Li, LBack);
sleep(10);
pnlLeft.Repaint;
Application.ProcessMessages;
end;
tbarWait.Enabled := True;
end;

procedure TMainForm.FormCreate(Sender: TObject);
// *** création de la fiche ***
begin
Duration := C_DefaultDuration;
tbarWait.Max := C_End;
tbarWait.Position := fDuration;
end;

procedure TMainForm.tbarWaitChange(Sender: TObject);
// *** changement de la vitesse ***
begin
Duration := tbarWait.Position;
end;

procedure TMainForm.SetDuration(AValue: Cardinal);
// *** détermination de la durée d'interpolation ***
begin
if fDuration = AValue then
Exit;
fDuration := AValue;
end;

function TMainForm.ComputeInterpolation(AInter: TInterpolationFunction; AStart,
AEnd, AStep: Single; ABack: Boolean): Single;
// *** calcul des interpolations ***
begin
if AStep < 0 then
Result := AStart
else
if AStep > Duration then
Result := AEnd
else
Result := AInter(AStart, AEnd, fDuration, AStep);
if ABack then
Result := AEnd - Result;
end;

function TMainForm.ComputeInterpolationInt(AInter: TInterpolationFunction;
AStart, AEnd, AStep: Integer; ABack: Boolean): Integer;
// *** interpolation entière ***
begin
Result := Round(ComputeInterpolation(AInter, AStart, AEnd, AStep, ABack));
end;

end.

À l'exécution, le résultat est exactement le même que celui obtenu avec une inclusion dans la classe de la fiche principale du projet. Comme notre objectif était de créer des classes autonomes, nous n'irons pas plus loin dans l'exploration de cette voie qui privilégie la programmation structurée sans passer par des classes et des objets.

Si le titre de ce chapitre pouvait sembler ironique, la souplesse de Pascal qui accepte aussi bien une programmation séquentielle qu'une approche POO (Programmation Orientée Objet) permet à chacun de s'y retrouver et de ne pas forcément mettre en œuvre des outils lourds pour des résultats médiocres !

V - Conclusion