Le but de ce programme a été pour moi de travailler avec la partie 3D de Firemonkey et d'une certaine manière promouvoir mon outil preferé.
A/ But et limites
Le Rubik's Cube est fonctionnel mais ne dispose pas la fonctionnalité resolution automatique.
Ceci a deja été fait et ne presente plus de fort interet.
Ceux qui s'attendent a un respect de la notation officielle des faces du Rubik's cube seront déçus.
Comment reperer la face AVANT apres des rotations des plans X Y ou Z.
J'ai donc utilisé la couleur du cube pivot (central à chaque face) pour decrire un plan.
Ceci est important pour la lecture du source. Mais aussi pour le jeu au clavier et avec les boutons colorés.
Par contre pour le jeu à la souris no souci !
les cubes coins sont sensibles au clic souris pour les rotations de face. Un tirer/Lacher sur la zone
grise permet une rotation de tout le cube.
Z- plan Rouge (avant au depart)
Z+ plan Orange (arriere au depart)
X+ plan Bleu (Droit au depart)
X- plan Vert (Gauche au depart)
Y- plan Blanc (Haut au depart)
Y+ plan Jaune (Bas au depart)
Source / Exemple :
unit UMain;
interface
// ______ __
// /\ ___\ /\ \__
// _____ \ \ \__/ ___ ___\ \ ,_\ __ ___ __ __ __ __
// /\ '__`\ \ \ _\/ __`\/' _ `\ \ \/ /'__`\/' _ `\ /'__`\ /'__`\ /\ \/\ \
// \ \ \_\ \ __ \ \ \/\ \_\ \\ \/\ \ \ \_/\ __//\ \/\ \/\ __//\ \_\ \_\ \ \_\ \
// \ \ ,__/ / \_\ \ \_\ \____/ \_\ \_\ \__\ \____\ \_\ \_\ \____\ \__/ \_\\ \____/
// \ \ \/ \/_/ \/_/\/___/ \/_/\/_/\/__/\/____/\/_/\/_/\/____/\/__/\/_/ \/___/
// \ \_\
// \/_/ P2F - 2012 - Rubik'sCube
// FREEWARE - Aucune diffusion commerciale basée sur ce code source
// n'est autorisée sans autorisation préalable de l'auteur.
// Rubik's Cube 3D Pascal Fonteneau
// Interfaces- Fun Parts - Pascal Fonteneau and Whiler
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Types3D, FMX.Objects3D,
FMX.Ani, FMX.Layouts, FMX.Memo, System.Math, System.StrUtils, FMX.Effects, FMX.Platform,
FMX.Objects, FMX.ListBox, FMX.Colors;
const
FUN_LABELS : array[0..11] of string = ('Damier', 'Barre', '2 Cubes',
'3 Cubes', 'Carré', 'Damier coloré',
'Plus', 'Bande', 'Diagonale',
'Angle', 'L', 'Tetris');
FUN_VALUES : array[0..11] of AnsiString =
('OORRWWYYGGBB', 'RRGGOORRGGor', 'WWGGRRwOOYBrBrBryOOw',
'GGyRRyowoRByWBOGGOWW', 'gBrOYwgB', 'OORRWWYYGGBBgBrOYwgFB',
'RGGBBRROOWWGGBBRROOYYr', 'rGGRRbyRRWRRBBRRYRRwbRRGG', 'GBROGBROGBRO',
'WGGRbGoRRBgYWWrGGw', 'OBBYRbGyoRyWgRRw', 'GBROGBROGBROWWYY');
ANIMATION : array[0..10] of String = ('Linear','Quadratic','Cubic','Quartic','Quintic','Sinusoidal',
'Exponential','Circular','Elastic','Back','Bounce');
type
TStickPosition = (PoFront, PoBack, PoTop, PoBottom, PoRight, PoLeft, PoNone);
TDirection = (DiNone, DiLeft, DiRight, DiTop, DiBottom);
TFormMain = class(TForm)
LabelX: TLabel;
LabelY: TLabel;
LabelZ: TLabel;
StyleBook1: TStyleBook;
Viewport3D2: TViewport3D;
CubeCentral: TCube;
RedPivot: TCube;
GreenPivot: TCube;
WhitePivot: TCube;
OrangePivot: TCube;
BluePivot: TCube;
YellowPivot: TCube;
BtnRedLeft: TButton;
BtnRedRight: TButton;
BtnGreenLeft: TButton;
BtnGreenRight: TButton;
BtnWhiteLeft: TButton;
BtnWhiteRight: TButton;
BtnOrangeLeft: TButton;
BtnOrangeRight: TButton;
BtnBlueLeft: TButton;
BtnBlueRight: TButton;
BtnYellowLeft: TButton;
BtnYellowRight: TButton;
FaTurnRed: TFloatAnimation;
FaTurnGreen: TFloatAnimation;
FaTurnWhite: TFloatAnimation;
FaTurnOrange: TFloatAnimation;
FaTurnYellow: TFloatAnimation;
Dummy: TDummy;
Camera: TCamera;
GbRotation: TGroupBox;
ArcDialX: TArcDial;
ArcDialY: TArcDial;
ArcDialZ: TArcDial;
FaTurnBlue: TFloatAnimation;
CC1: TCube;
CC2: TCube;
CC5: TCube;
CC3: TCube;
CC4: TCube;
CC6: TCube;
CC8: TCube;
CC7: TCube;
CA1: TCube;
CA2: TCube;
CA3: TCube;
CA4: TCube;
CA5: TCube;
CA6: TCube;
CA7: TCube;
CA8: TCube;
CA9: TCube;
CA10: TCube;
CA11: TCube;
CA12: TCube;
PLCC1R: TPlane;
PLCA1R: TPlane;
PLCA2R: TPlane;
PLCA3R: TPlane;
PLCA4R: TPlane;
PLCC2R: TPlane;
PLCC3R: TPlane;
PLCC4R: TPlane;
PLCC5W: TPlane;
PLCA9W: TPlane;
PLCC6W: TPlane;
PLCA5W: TPlane;
PLCA6W: TPlane;
PLCC1W: TPlane;
PLCA1W: TPlane;
PLCC2W: TPlane;
PLCC2B: TPlane;
PLCA6B: TPlane;
PLCC6B: TPlane;
PLCA3B: TPlane;
PLCA11B: TPlane;
PLCC3B: TPlane;
PLCA7B: TPlane;
PLCC7B: TPlane;
PLCC5V: TPlane;
PLCA5V: TPlane;
PLCC1V: TPlane;
PLCA10V: TPlane;
PLCA2V: TPlane;
PLCC8V: TPlane;
PLCA8V: TPlane;
PLCC4V: TPlane;
PLCC4J: TPlane;
PLCA4J: TPlane;
PLCC3J: TPlane;
PLCA8J: TPlane;
PLCA7J: TPlane;
PLCC8J: TPlane;
PLCA12J: TPlane;
PLCC7J: TPlane;
PLCC6O: TPlane;
PLCA9O: TPlane;
PLCC5O: TPlane;
PLCA11O: TPlane;
PLCA10O: TPlane;
PLCC7O: TPlane;
PLCA12O: TPlane;
PLCC8O: TPlane;
GbSize: TGroupBox;
BtnStart: TButton;
GBFun: TGroupBox;
LbInfoLink: TLabel;
ShadowEffectP2F: TShadowEffect;
cbFun: TComboBox;
sclytCommand: TScaledLayout;
recCommand: TRectangle;
pbFun: TProgressBar;
GbAnimation: TGroupBox;
CbAnimation: TComboBox;
tbSpeedAnim: TTrackBar;
CPnlSpeedAnimHint: TCalloutPanel;
txtSpeedAnimHint: TText;
FaSpeedAnimHint: TFloatAnimation;
TbSize: TTrackBar;
CbRotateX: TCheckBox;
CbRotateY: TCheckBox;
CbRotateZ: TCheckBox;
FaAutoRotateZ: TFloatAnimation;
FaAutoRotateY: TFloatAnimation;
FaAutoRotateX: TFloatAnimation;
LbSpeedAnimation: TLabel;
TbSpeedRotation: TTrackBar;
CPnlSpeedRotatHint: TCalloutPanel;
TxtSpeedRotateHint: TText;
FaSpeedRotateHint: TFloatAnimation;
LbSpeedRotate: TLabel;
CpnlCubeSize: TCalloutPanel;
TxtCubeSize: TText;
FaCubeSize: TFloatAnimation;
MainLight: TLight;
GbLight: TGroupBox;
RbAmbiance: TRadioButton;
RbSpotLight: TRadioButton;
BwTbLight: TBWTrackBar;
procedure ColorBtnClick(Sender: TObject);
procedure RotationFinish(Sender: TObject);
procedure ArcDialXChange(Sender: TObject);
procedure BtnStartClick(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
procedure StickCornerMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D);
procedure StickMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D);
procedure FormCreate(Sender: TObject);
procedure LbInfoLinkClick(Sender: TObject);
procedure Viewport3D2MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
procedure Viewport3D2MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
procedure Viewport3D2MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
procedure StickCornerMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single;
RayPos, RayDir: TVector3D);
procedure cbFunChange(Sender: TObject);
procedure RotatePlan(Pos1, Pos2: Single; TheDirection, Di1, Di2, Di3, Di4: TDirection;
P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12: AnsiChar);
procedure CbAnimationChange(Sender: TObject);
procedure tbSpeedAnimChange(Sender: TObject);
procedure TbSizeChange(Sender: TObject);
procedure CbRotateZChange(Sender: TObject);
procedure CbRotateXChange(Sender: TObject);
procedure CbRotateYChange(Sender: TObject);
procedure TbSpeedRotationChange(Sender: TObject);
procedure TbHorizontalChange(Sender: TObject);
procedure RbAmbianceChange(Sender: TObject);
procedure BwTbLightChange(Sender: TObject);
private
{ Déclarations privées }
LastDirection: ShortInt; // Sens de la dernière rotation 90 Aig -90 Inv
Direction: TDirection; // Sens du Mvt de la souris
ClickX: Single; // Emplacement du clic X initial
ClickY: Single; // Emplacement du clic Y initial
ClickXR: Single; // Emplacement du clic X Radial
ClickYR: Single; // Emplacement du clic Y Radial
MvtOn: Boolean; // Vaut Vrai lors d'un clic sur un stick
MvtAuto: Boolean; // Vaut Vrai lors des déplacements programmés
procedure Rotate(ThePivotAnimation: TFloatAnimation; StartValue, StepValue: Integer); overload;
procedure Rotate(Value: AnsiChar); overload;
procedure TurnXPlan(TheCube: TCube);
procedure TurnYPlan(TheCube: TCube);
procedure TurnZPlan(TheCube: TCube);
Function GetStickPosition(TheStick: TPlane): TStickPosition;
procedure MoveStick(TheStick: TPlane; Px, Py, Pz, Rx, Ry, Rz: Single);
procedure PlayMvt(TheMvts: AnsiString);
procedure SortRubik;
procedure SortCubes;
procedure SortCube(TheCube: TCube; Px, Py, Pz: Single);
procedure SortSticks;
procedure SetLight;
// function Translate(s: string): string;
public
{ Déclarations publiques }
end;
var
FormMain: TFormMain;
implementation
{$R *.fmx}
uses uWxPlatform;
procedure TFormMain.FormCreate(Sender: TObject);
var
iLoop: Integer;
begin
MvtOn := False; // pas de mouvement de la souris en cours
MvtAuto := False; // pas de mouvement automatique en cours
// Load the arrays
for iLoop := Low(FUN_LABELS) to High(FUN_LABELS) do
cbFun.Items.Add(FUN_LABELS[iLoop] + ' (' + IntToStr(Length(FUN_VALUES[iLoop])) + ')');
for iLoop := Low(ANIMATION) to High(ANIMATION) do
CbAnimation.Items.Add(ANIMATION[iLoop]);
CbAnimation.ItemIndex := 0;
CbAnimation.OnChange(Self);
tbSpeedAnim.Value := 1.6;
TbSpeedRotation.Value := 11;
TbSize.Value := 39;
end;
/// *************************************************
/// Rubik's cube camera zoom +/-
/// *************************************************
procedure TFormMain.TbSizeChange(Sender: TObject);
begin
FaCubeSize.Stop;
CpnlCubeSize.Opacity := 1;
TxtCubeSize.Text := Format('%2.0f', [TbSize.Value + 10]);
Camera.Position.Z := TbSize.Value - 60;
FaCubeSize.Start;
end;
// Rotation speed
procedure TFormMain.tbSpeedAnimChange(Sender: TObject);
var
speed: Single;
begin
FaSpeedAnimHint.Stop;
CPnlSpeedAnimHint.Opacity := 1;
speed := 2.1 - tbSpeedAnim.Value;
txtSpeedAnimHint.Text := Format('%2.1f s', [speed]);
FaTurnRed.Duration := speed;
FaTurnGreen.Duration := speed;
FaTurnWhite.Duration := speed;
FaTurnOrange.Duration := speed;
FaTurnYellow.Duration := speed;
FaTurnBlue.Duration := speed;
FaSpeedAnimHint.Start;
end;
// ****************************************
// Automatic speed rotation of the cube
// ****************************************
procedure TFormMain.TbSpeedRotationChange(Sender: TObject);
begin
FaSpeedRotateHint.Stop;
CPnlSpeedRotatHint.Opacity := 1;
TxtSpeedRotateHint.Text := Format('%2.1f s', [Abs(TbSpeedRotation.Value - 22)]);
FaAutoRotateX.Duration := 22 - TbSpeedRotation.Value;
FaAutoRotateY.Duration := 22 - TbSpeedRotation.Value;
FaAutoRotateZ.Duration := 22 - TbSpeedRotation.Value;
FaSpeedRotateHint.Start;
end;
procedure TFormMain.TbHorizontalChange(Sender: TObject);
begin
end;
// ******************************************************
// Lors du changement de l'animation
// ******************************************************
procedure TFormMain.CbAnimationChange(Sender: TObject);
begin
FaTurnRed.Interpolation := TInterpolationType(CbAnimation.ItemIndex);
FaTurnGreen.Interpolation := TInterpolationType(CbAnimation.ItemIndex);
FaTurnWhite.Interpolation := TInterpolationType(CbAnimation.ItemIndex);
FaTurnOrange.Interpolation := TInterpolationType(CbAnimation.ItemIndex);
FaTurnYellow.Interpolation := TInterpolationType(CbAnimation.ItemIndex);
FaTurnBlue.Interpolation := TInterpolationType(CbAnimation.ItemIndex);
end;
/// *************************************************
/// Common procedure for the click on a color button
/// *************************************************
procedure TFormMain.ColorBtnClick(Sender: TObject);
begin
cbFun.ItemIndex := -1;
Rotate(AnsiChar((Sender as TButton).Text[1]));
end;
/// *************************************************
/// Procedure de lancement de rotation au clavier
/// *************************************************
procedure TFormMain.FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
begin
Rotate(AnsiChar(KeyChar));
end;
/// *************************************************
/// Lance un appel au bon FloatAnimation et boucle en
/// Attendant la fin de l'animation
/// *************************************************
procedure TFormMain.Rotate(Value: AnsiChar);
begin
case Value of
'r': Rotate(FaTurnRed, 360, 270);
'R': Rotate(FaTurnRed, 0, 90);
'o': Rotate(FaTurnOrange, 0, 90);
'O': Rotate(FaTurnOrange, 360, 270);
'y': Rotate(FaTurnYellow, 0, 90);
'Y': Rotate(FaTurnYellow, 360, 270);
'b': Rotate(FaTurnBlue, 0, 90);
'B': Rotate(FaTurnBlue, 360, 270);
'g': Rotate(FaTurnGreen, 360, 270);
'G': Rotate(FaTurnGreen, 0, 90);
'w': Rotate(FaTurnWhite, 360, 270);
'W': Rotate(FaTurnWhite, 0, 90);
end;
end;
/// *****************************************************
/// Rechercher les cubes Coins et Arretes situés sur le
/// même plan que le cube allant pivoter et en faire ses
/// enfants.
/// Puis, lancement de l'animation ROTATION
/// *****************************************************
procedure TFormMain.Rotate(ThePivotAnimation: TFloatAnimation; StartValue, StepValue: Integer);
var
I: Integer;
begin
recCommand.Enabled := False; // desactiver le panneau de commandes
// récuperation du sens de la rotation
LastDirection := 90; // sens des aiguilles d'une montre pour 90 degres
if StartValue = 360 then
LastDirection := -90; // sens trigo
// Lier les cubes Coins et Arretes au cube allant pivoter
for I := 0 to FormMain.ComponentCount - 1 do
begin
if (FormMain.Components[I] Is TCube) then
begin
if ((FormMain.Components[I] As TCube).Tag = 2) or ((FormMain.Components[I] As TCube).Tag = 3) then
begin
// Red plan
if (SameValue((FormMain.Components[I] As TCube).Position.Z, -3, 0.1)) and (ThePivotAnimation = FaTurnRed) then
begin
(FormMain.Components[I] As TCube).Parent := RedPivot;
(FormMain.Components[I] As TCube).Position.Z := 0;
end;
// Orange plan
if (SameValue((FormMain.Components[I] As TCube).Position.Z, 3, 0.1)) and (ThePivotAnimation = FaTurnOrange) then
begin
(FormMain.Components[I] As TCube).Parent := OrangePivot;
(FormMain.Components[I] As TCube).Position.Z := 0;
end;
// Green plan
if (SameValue((FormMain.Components[I] As TCube).Position.X, -3, 0.1)) and (ThePivotAnimation = FaTurnGreen) then
begin
(FormMain.Components[I] As TCube).Parent := GreenPivot;
(FormMain.Components[I] As TCube).Position.X := 0;
end;
// Blue plan
if (SameValue((FormMain.Components[I] As TCube).Position.X, 3, 0.1)) and (ThePivotAnimation = FaTurnBlue) then
begin
(FormMain.Components[I] As TCube).Parent := BluePivot;
(FormMain.Components[I] As TCube).Position.X := 0;
end;
// White plan
if (SameValue((FormMain.Components[I] As TCube).Position.Y, -3, 0.1)) and (ThePivotAnimation = FaTurnWhite) then
begin
(FormMain.Components[I] As TCube).Parent := WhitePivot;
(FormMain.Components[I] As TCube).Position.Y := 0;
end;
// Yellow plan
if (SameValue((FormMain.Components[I] As TCube).Position.Y, 3, 0.1)) and (ThePivotAnimation = FaTurnYellow) then
begin
(FormMain.Components[I] As TCube).Parent := YellowPivot;
(FormMain.Components[I] As TCube).Position.Y := 0;
end;
end; // si cube Coin ou cuble arrete
end; // si un cube
end; // du for
// Valuer et lancer l'animation
ThePivotAnimation.StartValue := StartValue;
ThePivotAnimation.StopValue := StepValue;
ThePivotAnimation.Start;
while ThePivotAnimation.Running do Application.ProcessMessages;
end;
/// *************************************************
/// En fin d'animation ROTATION le cube ayant pivoté
// rend au cube central les enfants
/// *************************************************
procedure TFormMain.RotationFinish(Sender: TObject);
Var
I: Integer;
TmpX, TmpY, TmpZ: Single;
ThePlan: TCube;
begin
for I := 0 to FormMain.ComponentCount - 1 do
begin
if (FormMain.Components[I] Is TCube) then
begin
if ((FormMain.Components[I] As TCube).Tag = 2) or ((FormMain.Components[I] As TCube).Tag = 3) then
begin
if (FormMain.Components[I] As TCube).Parent <> CubeCentral then
begin
TmpX := (FormMain.Components[I] As TCube).AbsolutePosition.X;
TmpY := (FormMain.Components[I] As TCube).AbsolutePosition.Y;
TmpZ := (FormMain.Components[I] As TCube).AbsolutePosition.Z;
ThePlan := TCube((FormMain.Components[I] As TCube).Parent);
(FormMain.Components[I] As TCube).Position.X := TmpX;
(FormMain.Components[I] As TCube).Position.Y := TmpY;
(FormMain.Components[I] As TCube).Position.Z := TmpZ;
(FormMain.Components[I] As TCube).Parent := CubeCentral;
// plan Rouge (plan Z)
if ThePlan = RedPivot then
begin
(FormMain.Components[I] As TCube).Position.Z := -3;
TurnZPlan((FormMain.Components[I] As TCube));
end;
// plan Orange (Plan Z)
if ThePlan = OrangePivot then
begin
(FormMain.Components[I] As TCube).Position.Z := 3;
TurnZPlan((FormMain.Components[I] As TCube));
end;
// plan bleu (Plan X)
if ThePlan = BluePivot then
begin
(FormMain.Components[I] As TCube).Position.X := 3;
TurnXPlan((FormMain.Components[I] As TCube));
end;
// plan Vert (Plan X)
if ThePlan = GreenPivot then
begin
(FormMain.Components[I] As TCube).Position.X := -3;
TurnXPlan((FormMain.Components[I] As TCube));
end;
// plan Blanc (Plan Y)
if ThePlan = WhitePivot then
begin
(FormMain.Components[I] As TCube).Position.Y := -3;
TurnYPlan((FormMain.Components[I] As TCube));
end;
// plan Jaune (Plan Y)
if ThePlan = YellowPivot then
begin
(FormMain.Components[I] As TCube).Position.Y := 3;
TurnYPlan((FormMain.Components[I] As TCube));
end;
end;
end;
end;
if not MvtAuto then
recCommand.Enabled := True; // le panneau de commande redevient actif
end;
end;
/// *************************************************
/// Renvoi la position (le plan ) d'un stick
/// *************************************************
Function TFormMain.GetStickPosition(TheStick: TPlane): TStickPosition;
begin
Result := PoNone;
if SameValue(TheStick.Position.Y, -1.51, 0.1) then
Result := PoTop;
if SameValue(TheStick.Position.Y, 1.51, 0.1) then
Result := PoBottom;
if SameValue(TheStick.Position.X, -1.51, 0.1) then
Result := PoLeft;
if SameValue(TheStick.Position.X, 1.51, 0.1) then
Result := PoRight;
if SameValue(TheStick.Position.Z, 1.51, 0.1) then
Result := PoBack;
if SameValue(TheStick.Position.Z, -1.51, 0.1) then
Result := PoFront;
end;
/// *************************************************
/// Place (colle) un stick sur un plan des 6 plans
/// du cube
/// ************************************************
procedure TFormMain.MoveStick(TheStick: TPlane; Px, Py, Pz, Rx, Ry, Rz: Single);
begin
TheStick.ResetRotationAngle; // Remise à zero des angles de rotation IMPORTANT
TheStick.Position.X := Px;
TheStick.Position.Y := Py;
TheStick.Position.Z := Pz;
TheStick.RotationAngle.X := Rx;
TheStick.RotationAngle.Y := Ry;
TheStick.RotationAngle.Z := Rz;
end;
/// **********************************************
/// Pour le plan Z ( Rouge et Orange)
/// En fonction du dernier sens de rotation
/// replace les sticks sur la bonne facette
/// **********************************************
procedure TFormMain.TurnZPlan(TheCube: TCube);
var
I: Integer;
begin
for I := 0 to TheCube.ChildrenCount - 1 do
begin
if LastDirection = 90 then // sens des aiguilles
begin
if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoTop then
MoveStick((TheCube.Children[I] AS TPlane), 1.51, 0, 0, 0, -90, 0) // Right
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoLeft then
MoveStick((TheCube.Children[I] AS TPlane), 0, -1.51, 0, -90, 0, 0) // Up
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBottom then
MoveStick((TheCube.Children[I] AS TPlane), -1.51, 0, 0, 0, 90, 0) // Left
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoRight then
MoveStick((TheCube.Children[I] AS TPlane), 0, 1.51, 0, 90, 0, 0); // Down
end
else
begin
if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoTop then
MoveStick((TheCube.Children[I] AS TPlane), -1.51, 0, 0, 0, 90, 0) // Left
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoLeft then
MoveStick((TheCube.Children[I] AS TPlane), 0, 1.51, 0, 90, 0, 0) // Down
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBottom then
MoveStick((TheCube.Children[I] AS TPlane), 1.51, 0, 0, 0, -90, 0) // Right
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoRight then
MoveStick((TheCube.Children[I] AS TPlane), 0, -1.51, 0, -90, 0, 0); // Up
end;
end;
end;
/// / **********************************************
/// Idem, pour le plan X ( Bleu et vert)
/// / **********************************************
procedure TFormMain.TurnXPlan(TheCube: TCube);
var
I: Integer;
begin
for I := 0 to TheCube.ChildrenCount - 1 do
begin
if LastDirection = 90 then // sens INVERSE des aiguilles
begin
if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoTop then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, -1.51, 0, 0, 0) // Front
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBack then
MoveStick((TheCube.Children[I] AS TPlane), 0, -1.51, 0, -90, 0, 0) // Up
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBottom then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, 1.51, 180, 0, 0) // Back
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoFront then
MoveStick((TheCube.Children[I] AS TPlane), 0, 1.51, 0, 90, 0, 0); // Down
end
else
begin
if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoTop then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, 1.51, 180, 0, 0) // Back
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBack then
MoveStick((TheCube.Children[I] AS TPlane), 0, 1.51, 0, 90, 0, 0) // Down
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBottom then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, -1.51, 0, 0, 0) // Front
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoFront then
MoveStick((TheCube.Children[I] AS TPlane), 0, -1.51, 0, -90, 0, 0); // Up
end;
end;
end;
/// **********************************************
/// Idem, pour le plan Y ( Blanc et Jaune)
/// **********************************************
procedure TFormMain.TurnYPlan(TheCube: TCube);
var
I: Integer;
begin
for I := 0 to TheCube.ChildrenCount - 1 do
begin
if LastDirection = 90 then // sens des aiguilles
begin
if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoFront then
MoveStick((TheCube.Children[I] AS TPlane), -1.51, 0, 0, 0, 90, 0) // Left
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoLeft then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, 1.51, 180, 0, 0) // Back
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBack then
MoveStick((TheCube.Children[I] AS TPlane), 1.51, 0, 0, 0, -90, 0) // Right
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoRight then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, -1.51, 0, 0, 0); // Front
end
else
begin
if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoFront then
MoveStick((TheCube.Children[I] AS TPlane), 1.51, 0, 0, 0, -90, 0) // Right
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoLeft then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, -1.51, 0, 0, 0) // Front
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoBack then
MoveStick((TheCube.Children[I] AS TPlane), -1.51, 0, 0, 0, 90, 0) // Left
else if GetStickPosition((TheCube.Children[I] AS TPlane)) = PoRight then
MoveStick((TheCube.Children[I] AS TPlane), 0, 0, 1.51, 180, 0, 0); // Back
end;
end;
end;
{$REGION 'Jeu à la souris'}
/// **********************************************
/// en cliquant sur un stick on arme la recherche
/// d'un mouvement
/// **********************************************
procedure TFormMain.StickMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single;
RayPos, RayDir: TVector3D);
begin
if MvtAuto then
Exit;
ClickXR := RayDir.X; // emplacement du clic en X Radial
ClickYR := RayDir.Y; // emplacement du clic en Y Radial
MvtOn := True;
end;
/// **********************************************
/// En cliquant sur le fond on arme la rotation
/// du cube
/// **********************************************
procedure TFormMain.Viewport3D2MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
ClickX := X; // emplacement du clic en X
ClickY := Y; // emplacement du clic en Y
end;
/// **********************************************
/// le deplacement de la souris fait pivoter le cube
/// **********************************************
procedure TFormMain.Viewport3D2MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
IF MvtOn then
Exit;
if ssLeft in Shift then // si la souris bouge avec le clic droit enfoncé
begin
Direction := DiNone;
IF (ClickX - X) > 3 then
Direction := DiLeft;
IF (X - ClickX) > 3 then
Direction := DiRight;
IF (ClickY - Y) > 3 then
Direction := DiTop;
IF (Y - ClickY) > 3 then
Direction := DiBottom;
if Direction <> DiNone then // une direction a été trouvée
begin
ClickX := X;
ClickY := Y;
if Direction = DiBottom then
ArcDialX.Value := ArcDialX.Value - 5;
if Direction = DiTop then
ArcDialX.Value := ArcDialX.Value + 5;
if Direction = DiLeft then
ArcDialY.Value := ArcDialY.Value - 5;
if Direction = DiRight then
ArcDialY.Value := ArcDialY.Value + 5;
ArcDialXChange(Self);
end;
end;
end;
/// **********************************************
/// Desarmement du mouvement
/// **********************************************
procedure TFormMain.Viewport3D2MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
MvtOn := False;
end;
procedure TFormMain.StickCornerMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single;
RayPos, RayDir: TVector3D);
begin
MvtOn := False;
end;
procedure TFormMain.StickCornerMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single; RayPos, RayDir: TVector3D);
var
PosXCube, PosYCube, PosZCube: int64;
begin
if not MvtOn then
Exit; // si pas de mouvement d'armé
if ssLeft in Shift then // si la souris bouge avec le clic droit enfoncé
begin
Direction := DiNone;
IF (ClickXR - RayDir.X) > 0.02 then
Direction := DiLeft;
IF (RayDir.X - ClickXR) > 0.02 then
Direction := DiRight;
IF (ClickYR - RayDir.Y) > 0.02 then
Direction := DiTop;
IF (RayDir.Y - ClickYR) > 0.02 then
Direction := DiBottom;
if Direction <> DiNone then // une direction a été trouvée
begin
cbFun.ItemIndex := -1;
// recup de la position XYU du cube sous le stick
PosXCube := Round(((Sender as TPlane).Parent as TCube).Position.X);
PosYCube := Round(((Sender as TPlane).Parent as TCube).Position.Y);
PosZCube := Round(((Sender as TPlane).Parent as TCube).Position.Z);
// desarment du mouvement et reinitialisation de l'emplacement de la souris
MvtOn := False;
ClickXR := RayDir.X;
ClickYR := RayDir.Y;
if (PosZCube = -3) and (SameValue((Sender as TPlane).Position.Z, -1.51, 0.1)) then // Plan Avant Rouge
RotatePlan(PosXCube, PosYCube, Direction, DiTop, DiBottom, DiRight, DiLeft, 'g', 'G', 'w', 'W', 'Y', 'y', 'B', 'b', 'w',
'W', 'Y', 'y')
else if (PosZCube = 3) and (SameValue((Sender as TPlane).Position.Z, 1.51, 0.1)) then // Plan Arriere Orange
RotatePlan(PosXCube, PosYCube, Direction, DiTop, DiBottom, DiRight, DiLeft, 'g', 'G', 'W', 'w', 'y', 'Y', 'B', 'b', 'W',
'w', 'y', 'Y')
else if (PosYCube = -3) and (SameValue((Sender as TPlane).Position.Y, -1.51, 0.1)) then // Plan Haut Blanc
RotatePlan(PosXCube, PosZCube, Direction, DiTop, DiBottom, DiRight, DiLeft, 'g', 'G', 'R', 'r', 'o', 'O', 'B', 'b', 'R',
'r', 'o', 'O')
else if (PosYCube = 3) and (SameValue((Sender as TPlane).Position.Y, 1.51, 0.1)) then // Plan Bas Jaune
RotatePlan(PosXCube, PosZCube, Direction, DiTop, DiBottom, DiRight, DiLeft, 'g', 'G', 'r', 'R', 'O', 'o', 'B', 'b', 'r',
'R', 'O', 'o')
else if (PosXCube = -3) and (SameValue((Sender as TPlane).Position.X, -1.51, 0.1)) then // Plan Gauche Vert
RotatePlan(PosYCube, PosZCube, Direction, DiRight, DiLeft, DiBottom, DiTop, 'w', 'W', 'r', 'R', 'O', 'o', 'Y', 'y', 'r',
'R', 'O', 'o')
else if (PosXCube = 3) and (SameValue((Sender as TPlane).Position.X, 1.51, 0.1)) then // Plan Droit Bleu
RotatePlan(PosYCube, PosZCube, Direction, DiRight, DiLeft, DiBottom, DiTop, 'w', 'W', 'R', 'r', 'o', 'O', 'Y', 'y', 'R',
'r', 'o', 'O');
end;
end;
end;
procedure TFormMain.RotatePlan(Pos1, Pos2: Single; TheDirection, Di1, Di2, Di3, Di4: TDirection;
P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12: AnsiChar);
begin
if Pos1 = -3 then
begin
if (TheDirection = Di1) then
Rotate(P1);
if (TheDirection = Di2) then
Rotate(P2);
if Pos2 = -3 then
begin
if (TheDirection = Di3) then
Rotate(P3);
if (TheDirection = Di4) then
Rotate(P4);
end
else
begin
if (TheDirection = Di3) then
Rotate(P5);
if (TheDirection = Di4) then
Rotate(P6);
end;
end
else
begin // coté bas
if (TheDirection = Di1) then
Rotate(P7);
if (TheDirection = Di2) then
Rotate(P8);
if Pos2 = -3 then
begin
if (TheDirection = Di3) then
Rotate(P9);
if (TheDirection = Di4) then
Rotate(P10);
end
else
begin
if (TheDirection = Di3) then
Rotate(P11);
if (TheDirection = Di4) then
Rotate(P12);
end;
end;
end;
{$ENDREGION}
{$REGION 'Sort the cube (not solving)'}
procedure TFormMain.BtnStartClick(Sender: TObject);
begin
if MessageDlg('Restore initial cube?', TMsgDlgType.mtInformation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0) = MrNo then
Exit;
SortRubik();
CbRotateX.IsChecked := False;
CbRotateY.IsChecked := False;
CbRotateZ.IsChecked := False;
ArcDialX.Value := -35;
ArcDialY.Value := -45;
ArcDialZ.Value := -18;
cbFun.ItemIndex := -1;
ArcDialXChange(Self);
end;
procedure TFormMain.SortRubik();
begin
SortCubes();
SortSticks();
end;
procedure TFormMain.SortSticks();
begin
// face Rouge
MoveStick( PLCC1R, 0, 0, -1.51, 0, 0, 0);
MoveStick( PLCC2R, 0, 0, -1.51, 0, 0, 0);
MoveStick( PLCC4R, 0, 0, -1.51, 0, 0, 0);
MoveStick( PLCC3R, 0, 0, -1.51, 0, 0, 0);
MoveStick( PLCA1R, 0, 0, -1.51, 0, 0, 0);
MoveStick( PLCA2R, 0, 0, -1.51, 0, 0, 0);
MoveStick( PLCA3R, 0, 0, -1.51, 0, 0, 0);
MoveStick( PLCA4R, 0, 0, -1.51, 0, 0, 0);
// Face Bleu
MoveStick( PLCC2B, 1.51, 0, 0, 0, -90, 0);
MoveStick( PLCC6B, 1.51, 0, 0, 0, -90, 0);
MoveStick( PLCC3B, 1.51, 0, 0, 0, -90, 0);
MoveStick( PLCC7B, 1.51, 0, 0, 0, -90, 0);
MoveStick( PLCA6B, 1.51, 0, 0, 0, -90, 0);
MoveStick( PLCA3B, 1.51, 0, 0, 0, -90, 0);
MoveStick(PLCA11B, 1.51, 0, 0, 0, -90, 0);
MoveStick( PLCA7B, 1.51, 0, 0, 0, -90, 0);
// Face Verte
MoveStick( PLCC5V, -1.51, 0, 0, 0, 90, 0);
MoveStick( PLCC1V, -1.51, 0, 0, 0, 90, 0);
MoveStick( PLCC8V, -1.51, 0, 0, 0, 90, 0);
MoveStick( PLCC4V, -1.51, 0, 0, 0, 90, 0);
MoveStick( PLCA5V, -1.51, 0, 0, 0, 90, 0);
MoveStick(PLCA10V, -1.51, 0, 0, 0, 90, 0);
MoveStick( PLCA8V, -1.51, 0, 0, 0, 90, 0);
MoveStick( PLCA2V, -1.51, 0, 0, 0, 90, 0);
// Orange
MoveStick( PLCC6O, 0, 0, 1.51, 180, 0, 0);
MoveStick( PLCC5O, 0, 0, 1.51, 180, 0, 0);
MoveStick( PLCC7O, 0, 0, 1.51, 180, 0, 0);
MoveStick( PLCC8O, 0, 0, 1.51, 180, 0, 0);
MoveStick( PLCA9O, 0, 0, 1.51, 180, 0, 0);
MoveStick(PLCA11O, 0, 0, 1.51, 180, 0, 0);
MoveStick(PLCA10O, 0, 0, 1.51, 180, 0, 0);
MoveStick(PLCA12O, 0, 0, 1.51, 180, 0, 0);
// Face Blanche
MoveStick( PLCC5W, 0, -1.51, 0, -90, 0, 0);
MoveStick( PLCC6W, 0, -1.51, 0, -90, 0, 0);
MoveStick( PLCC1W, 0, -1.51, 0, -90, 0, 0);
MoveStick( PLCC2W, 0, -1.51, 0, -90, 0, 0);
MoveStick( PLCA9W, 0, -1.51, 0, -90, 0, 0);
MoveStick( PLCA5W, 0, -1.51, 0, -90, 0, 0);
MoveStick( PLCA6W, 0, -1.51, 0, -90, 0, 0);
MoveStick( PLCA1W, 0, -1.51, 0, -90, 0, 0);
// Face Jaune
MoveStick( PLCC4J, 0, 1.51, 0, 90, 0, 0);
MoveStick( PLCC3J, 0, 1.51, 0, 90, 0, 0);
MoveStick( PLCC8J, 0, 1.51, 0, 90, 0, 0);
MoveStick( PLCC7J, 0, 1.51, 0, 90, 0, 0);
MoveStick( PLCA4J, 0, 1.51, 0, 90, 0, 0);
MoveStick( PLCA8J, 0, 1.51, 0, 90, 0, 0);
MoveStick( PLCA7J, 0, 1.51, 0, 90, 0, 0);
MoveStick(PLCA12J, 0, 1.51, 0, 90, 0, 0);
end;
procedure TFormMain.SortCubes();
begin
// les cubes coins
SortCube( CC1, -3, -3, -3);
SortCube( CC2, 3, -3, -3);
SortCube( CC3, 3, 3, -3);
SortCube( CC4, -3, 3, -3);
SortCube( CC5, -3, -3, 3);
SortCube( CC6, 3, -3, 3);
SortCube( CC7, 3, 3, 3);
SortCube( CC8, -3, 3, 3);
// les cubes arretes
SortCube( CA1, 0, -3, -3);
SortCube( CA2, -3, 0, -3);
SortCube( CA3, 3, 0, -3);
SortCube( CA4, 0, 3, -3);
SortCube( CA5, -3, -3, 0);
SortCube( CA6, 3, -3, 0);
SortCube( CA7, 3, 3, 0);
SortCube( CA8, -3, 3, 0);
SortCube( CA9, 0, -3, 3);
SortCube(CA10, -3, 0, 3);
SortCube(CA11, 3, 0, 3);
SortCube(CA12, 0, 3, 3);
end;
procedure TFormMain.SortCube(TheCube: TCube; Px, Py, Pz: Single);
begin
TheCube.ResetRotationAngle;
TheCube.Position.X := Px;
TheCube.Position.Y := Py;
TheCube.Position.Z := Pz;
end;
{$ENDREGION}
{$REGION 'Jouer des mouvements enregistrés'}
procedure TFormMain.cbFunChange(Sender: TObject);
begin
if cbFun.ItemIndex <> -1 then
begin
cbFun.DropDown; // Pour refermer immédiatement la dropdown
SortRubik();
PlayMvt(FUN_VALUES[cbFun.ItemIndex]);
end;
end;
procedure TFormMain.PlayMvt(TheMvts: AnsiString);
var
I: Integer;
iMax: Integer;
begin
// Platform.SetCursor(nil, crHourGlass); // ProcessMessages casse le curseur
MvtAuto := True; // On enchaine une série (l'utilisateur ne peut pas faire de modif manuelle)
iMax := Length(TheMvts);
pbFun.Value := 1;
pbFun.Max := iMax;
pbFun.Visible := True;
recCommand.Enabled := False; // desactiver le panneau de commandes
for I := 1 to iMax do
begin
Rotate(TheMvts[I]);
pbFun.Value := pbFun.Value + 1;
end;
MvtAuto := False; // La série est terminée, l'utilisateur peut à nouveau jouer
pbFun.Visible := False;
recCommand.Enabled := True; // le panneau de commande redevient actif
end;
{$ENDREGION}
{$REGION 'Ouverture de la page Web'}
procedure TFormMain.LbInfoLinkClick(Sender: TObject);
begin
TMisc.Open((Sender as TLabel).Text);
end;
{$ENDREGION}
{$REGION 'Rotation automatique du cube'}
/// *************************************************
/// Déplacement de la caméra autour du rubik's cube
/// *************************************************
Procedure TFormMain.ArcDialXChange(Sender: TObject);
begin
Dummy.RotationAngle.X := ArcDialX.Value;
Dummy.RotationAngle.Y := ArcDialY.Value;
Dummy.RotationAngle.Z := ArcDialZ.Value;
end;
Procedure TFormMain.CbRotateXChange(Sender: TObject);
begin
FaAutoRotateX.Stop;
if CbRotateX.IsChecked then
FaAutoRotateX.Start;
end;
Procedure TFormMain.CbRotateYChange(Sender: TObject);
begin
FaAutoRotateY.Stop;
if CbRotateY.IsChecked then
FaAutoRotateY.Start;
end;
Procedure TFormMain.CbRotateZChange(Sender: TObject);
begin
FaAutoRotateZ.Stop;
if CbRotateZ.IsChecked then
FaAutoRotateZ.Start;
end;
{$ENDREGION}
{$REGION 'Les lumières'}
procedure TFormMain.RbAmbianceChange(Sender: TObject);
Var
I: Integer;
Color: TAlphaColor;
begin
MainLight.Enabled := RbSpotLight.IsChecked;
BwTbLight.Enabled := RbSpotLight.IsChecked;
if RbSpotLight.IsChecked then
begin
Color := claNull;
SetLight;
end
else
Color := claWhite;
for I := 0 to FormMain.ComponentCount - 1 do
begin
// les stickers
if (FormMain.Components[I] Is TPlane) then
(FormMain.Components[I] As TPlane).material.Emissive := Color;
// Les cubes pivots
if (FormMain.Components[I] Is TCube) then
if (FormMain.Components[I] As TCube).Tag = 1 then
(FormMain.Components[I] As TCube).material.Emissive := Color;
end;
end;
procedure TFormMain.SetLight;
var
cLight: TAlphaColorRec;
begin
cLight.R := Round(255 * BwTbLight.Value);
cLight.G := Round(255 * BwTbLight.Value);
cLight.B := Round(255 * BwTbLight.Value);
MainLight.Diffuse := cLight.Color;
Viewport3D2.Repaint;
end;
procedure TFormMain.BwTbLightChange(Sender: TObject);
begin
SetLight;
end;
{$ENDREGION}
end.
Conclusion :
A/ Caméra et Dummy
Premier point important. Il existe 2 façons de voir un objet sous tous ses angles.
Soit faire pivoter l'objet (rotationAngle)
Soit pivoter autour de l'objet en continuant à le fixer (camera et Dummy)
Le premier cas convient tres bien avec un seul objet. Seuls les rotationAngle XYZ varient.
Dans tous les autres cas utilisez la seconde méthode. Ainsi les coordonnées X,Y et Z des objets resteront
bien mieux maitrisables.
B/ Notions diverses
Le rubik est composé :
-D'un cube central (TCube) celui qui est invisible.
-De 6 cubes Pivots (TCube) sitée au centre,avec un rendu Bitmap pour la couleur
-6 Composants anitmations (TFloatAnimation) , un par pivot. Ils sont enfant des Pivots et mémorisent l'axe de rotation du pivot.
-8 Cubes coins (TCube) Noir avec 3 facettes(TPlane) chacuns. Nommé Stickers dans le source et les commentaires.
-12 cubes Arretes (TCube) Noir avec 2 facettes(TPlane) chacuns. Nommé Stickers dans le source et les commentaires.
C.1 Une fiche dispose de composants accèssibles depuis toujours avec ComponentCount et Components[]. Les objets 3D
eux gerent des enfants accessibles avec ChildrenCount et Children[]. Pensez y !
C.2 Rotation des enfants
La rotation d'un parent declenche la rotation de ses enfants autour du même axe (principe utilisé dans le prog).
A l'issue de la rotation, les enfants ont gardé les mêmes positions X,Y,Z et rotationAngle puisque directement
lié a leur parent. Bien retenir que les positions X,Y,Z et rotationAngle sont donc relatif au parent.
Seules les positions Absolues Varient.
C.3 Changement de parent
Pour effectuer une rotation le programme recherche les cubes coins et cubes arrêtes situés sur le même plan que
le cube central devant pivoter et en fait ses enfants, Puis s'effectue la rotation. Enfin, les cubes enfants
sont rendu au cube central.
Problème, le changement de parent replace l'enfant avec le même décalage et la même rotation qu'il avait avec son ex parent(4.2).
les rotationsAngles et le repositionnement relatif au cube centrale doivent être refait (c'est a vous de le gerer)
C.4 Animation en Thread
Les animations sont threadées, il faut donc attendre la fin d'une rotation pour en lancer une autre
C.6 Pour finir
C.6 Pour finir
les cubes coins sont sensibles au clic souris pour les rotations de face. Un tirer-lâcher sur la zone
grise permet une rotation de tout le cube.
La partie 'Fun' exécute des figures pré-enregistrées.
Modifiez la taille de la fenêtre, changez l'éclairage, changez les animations, vitesse, jouez les auto-rotations
et vous verrez la puissance de l'outil Firemonkey
Merci à Whiler pour son Aide.
Pour ceux qui veulent tester l'exe
ftp://ftp2.p2f-logiciels.com/pflogicie/SetupRubik.exe
Pascal Fonteneau Alias Fireman
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.