unit Unit1;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  System.TypInfo,
  Vcl.Graphics,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.Controls,
  Vcl.StdCtrls,
  Vcl.ExtCtrls,
  Vcl.Samples.Spin,
  
  GLScene,
  GLVectorTypes,
  GLObjects,
  GLCoordinates,
  GLSceneViewer,
  GLCrossPlatform,
  GLBaseClasses,
  GLCadencer,
  GLAnimationUtils,
  GLVectorGeometry,
  GLBitmapFont,
  GLWindowsFont;

type

  TSinglePoint = record
    X: single;
    Y: single;
  end;

  // You can customize TAnimationState for your own purpose, you can add states or rename them, etc.
  TAnimationState = (asWaiting, asGoToStateA, asGoToStateB);

  TAnimationTime = record
    Current: Double;
    Initial: Double;
    Diff: Double;
  end;

  // You can customize TAnimation for your own purpose, you can add InitialXValue for your custom data (Matrix or anything else).
  // Note that you will maybe have to create your own Tweener function to use your custom Current/Target type.
  TAnimation = record
    State: TAnimationState;
    Time: TAnimationTime;
    Init: Boolean;
    Initial1iValue: Integer;
    Initial1sValue: single;
    Initial3fValue: TAffineVector;
    Initial4fValue: TVector;
    InitialPtValue: TSinglePoint;
  end;

  TForm1 = class(TForm)
    GLScene1: TGLScene;
    GLSceneViewer1: TGLSceneViewer;
    GLCamera1: TGLCamera;
    GLPlane1: TGLPlane;
    Panel1: TPanel;
    Button1: TButton;
    GLCadencer1: TGLCadencer;
    Button2: TButton;
    PointA: TGLPoints;
    GLFlatText1: TGLFlatText;
    GLWindowsBitmapFont1: TGLWindowsBitmapFont;
    PointB: TGLPoints;
    GLFlatText2: TGLFlatText;
    UseCurrentPosition: TCheckBox;
    EaseTypeA: TComboBox;
    EaseTypeB: TComboBox;
    TimeA: TSpinEdit;
    TimeB: TSpinEdit;
    Label2: TLabel;
    Label1: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    procedure GLPlane1Progress(Sender: TObject; const deltaTime, newTime: Double);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
  private
    FGLPlane1AnimationData: TAnimation;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  FGLPlane1AnimationData.State := asGoToStateA;
  FGLPlane1AnimationData.Init := True;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  FGLPlane1AnimationData.State := asGoToStateB;
  FGLPlane1AnimationData.Init := True;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  EaseName: string;
  i: integer;
begin
  // Fill combobox with human readable data from TEaseType
  for i := Ord(etLinear) to Ord(etBounceOutIn) do
  begin
    EaseName := GetEnumName(TypeInfo(TEaseType), Ord(TEaseType(i)));
    EaseTypeA.Items.Add(EaseName);
    EaseTypeB.Items.Add(EaseName);
  end;

  EaseTypeA.ItemIndex := Ord(etLinear);
  EaseTypeB.ItemIndex := Ord(etLinear);
end;

procedure TForm1.GLPlane1Progress(Sender: TObject; const deltaTime, newTime: Double);
begin

  // Execute following code only if we are in state asGoToStateA
  if FGLPlane1AnimationData.State = asGoToStateA then
  begin
    // First we initialize value if requested
    if FGLPlane1AnimationData.Init = True then
      with FGLPlane1AnimationData do
      begin
        Init := False;
        Time.Initial := newTime;

        if UseCurrentPosition.Checked then
          Initial3fValue := GLPlane1.Position.AsAffineVector
        else
          Initial3fValue := PointB.Position.AsAffineVector;
      end;

    // Then we use the Tweener function to compute interpolation based on Initial values (time and position) and current ones
    with FGLPlane1AnimationData do
    begin
      Time.Diff := newTime - Time.Initial;
      GLPlane1.Position.AsAffineVector := Tweener(Initial3fValue, PointA.Position.AsAffineVector, Time.Diff, TimeA.Value/1000, TEaseType(EaseTypeA.ItemIndex));

      if Time.Diff >= TimeA.Value/1000 then
        State := asWaiting;
    end;
  end;

  // Execute following code only if we are in state asGoToStateB
  if FGLPlane1AnimationData.State = asGoToStateB then
  begin
    // First we initialize value if requested
    if FGLPlane1AnimationData.Init = True then
      with FGLPlane1AnimationData do
      begin
        Init := False;
        Time.Initial := newTime;

        if UseCurrentPosition.Checked then
          Initial3fValue := GLPlane1.Position.AsAffineVector
        else
          Initial3fValue := PointA.Position.AsAffineVector;
      end;

    // Then we use the Tweener function to compute interpolation based on Initial values (time and position) and current ones
    with FGLPlane1AnimationData do
    begin
      Time.Diff := newTime - Time.Initial;
      GLPlane1.Position.AsAffineVector := Tweener(Initial3fValue, PointB.Position.AsAffineVector, Time.Diff, TimeB.Value/1000, TEaseType(EaseTypeB.ItemIndex));

      if Time.Diff >= TimeB.Value/1000 then
        State := asWaiting;
    end;
  end;

end;

procedure TForm1.GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if ssCtrl in Shift then
  begin
    if (Button = TMouseButton.mbLeft) then
    begin
      PointA.Position.X := X;
      PointA.Position.Y := GLSceneViewer1.Height-Y;
    end
    else if (Button = TMouseButton.mbRight) then
    begin
      PointB.Position.X := X;
      PointB.Position.Y := GLSceneViewer1.Height-Y;
    end;
  end;
end;

end.
