3Dメッシュ(TMesh)をキーボード操作に応答してアニメーション表示する(FMX) ~Delphiでお手軽プログラミング

3Dメッシュ(TMesh)をキーボード操作に応答してアニメーション表示する(FMX) ~Delphiでお手軽プログラミング

キーボードの上矢印↑キーで前進、左矢印←キーで左回転、右矢印→キーで左回転、スペースキーでジャンプする、 以下のようなアプリケーションを作成します。

Delphiを起動して新規作成を行い、必要なコンポーネントをドラッグ&ドロップする

Delphi起動⇒ファイル⇒新規作成⇒マルチデバイスアプリケーションを選択し、「空のアプリケーション」を選択してOKボタンをクリックします。

「TPanel」をフォームにドラッグ&ドロップし、プロパティ「Align」を[Top]に設定します。
Panel1に「TSwitch」をドラッグ&ドロップします。
「TViewport3D」をフォームにドラッグ&ドロップし、プロパティ「Align」を[Client]に設定します。
TTimerをフォームへドラッグ&ドロップします。

ユニットの追加(Unit2)

「ファイル」⇒「新規作成」⇒「ユニット Delphi」から新しいユニットを作成し、以下のソースコードを記述します。
unit Unit2;

interface
uses
  System.Classes,System.SysUtils,
  FMX.Types,FMX.Forms,
  FMX.Types3D, System.Math.Vectors,
  FMX.Objects3D, FMX.MaterialSources,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Controls3D,
  FMX.Viewport3D
  ;

type
  T3DState=(Normal,Walk,Jump);

  T3DActionAngle=record
    Step,LegLN,LegRN,LegLT,LegRT,ArmLE,ArmRE,ArmLH,ArmRH,BodyY,Angle,x,z:Single;
  end;
  T3DActionAngles=array of T3DActionAngle;
  P3DActionAngles=^T3DActionAngles;

  TBase3D=class(TObject)
  private
    FParent:TFMXObject;
    FDBase:TDummy;//全体
    FDArmLE:TDummy;//左肩から肘
    FDArmRE:TDummy;
    FDArmLH:TDummy;//右肘から
    FDArmRH:TDummy;
    FDLegLN:TDummy;//ももから
    FDLegRN:TDummy;
    FDLegLT:TDummy;//膝から
    FDLegRT:TDummy;
    FBody:TMesh; //胴体
    FHead:TMesh; //頭
    FArmLE:TMesh;//L肩から肘
    FArmRE:TMesh;//R肩から肘
    FArmLH:TMesh;//L肘から手
    FArmRH:TMesh;//R肘から手
    FLegLN:TMesh;//Lももから膝
    FLegRN:TMesh;//Rももから膝
    FLegLT:TMesh;//L膝からつま先
    FLegRT:TMesh;//R膝からつま先
    FDisk:TDisk;
    FCamera:TCamera;
    FMate:TLightMaterialSource;
    FDir:Single;
    FState:T3DState;

    FNormal:T3DActionAngles;
    FWalk:T3DActionAngles;
    FJump:T3DActionAngles;
    FOldAct:T3DActionAngle;
    FNowAct:T3DActionAngle;//現在のアクション
{
  ボーン構成(TDummyはボールジョイント)
    FCamera
      │
    (TDummy)               FHead
    FDBase ───────┐ │
                         │ │
                         │ │
              (TDummy)   │ │    (TDummy)
    FArmLE ─ FDArmLE ─ FBody ─ FDArmRE ─ FArmRE
      │                  │                   │
    (TDummy)              │                 (TDummy)
    FDArmLH               │                 FDArmRH
      │                  │                   │
    FArmLH                │                 FArmLH
                 (TDummy) │  (TDummy)
                 FDLegLN─┴─FDLegRN
                   │           │
                 FLegLN       FLegRN
                   │           │
                 (TDummy)     (TDummy)
                 FDLegLT      FDLegRT
                   │           │
                 FLegLT       FLegRT

  X:→ Y:↓ Z:奥行き方向が正
  変更したければTDummyをTViewport3Dに配置し、
  Scale.Yを-1(Y:↑方向になる)にたり、
  Scale.Zを-1(手前方向が正となる)してローカル座標空間を作り、
  配置する全てのオブジェクトの親をこのTDummuとすればよい

  FDBase
    RotationAngle.Y  方位
  FDArmLE FDArmRE
    RotationAngle.X  >0で肩前方向回転
  FDArmLH FDArmLH
    RotationAngle.X  >0で肘前方向回転(<0はない)
  FDLegLN FDLegLN
    RotationAngle.X  >0で太もも前方向回転
  FDLegLT FDLegLT
    RotationAngle.X  <0で膝を曲げる(>0はない)
}
    procedure SetAction();
    procedure OriginalRender(Sender:TObject;Context:TContext3D);
  public
    Constructor Create(parent:TFMXObject);
    Destructor Destroy();override;
    procedure Action(state:T3DState;RelativeAngle,Stride:Single);
    property State:T3DState read FState;
    property Camera:TCamera read FCamera;
  end;


implementation

{ TBase3D }

uses Unit1;

procedure TBase3D.Action(state:T3DState;RelativeAngle,Stride:Single);
var PAct:P3DActionAngles;
    i,l:Integer;
    rate,deg:Single;
begin
{
  FMX.Ani.TAnimator.AnimateFloatDelay
  を使いたいが、RotationAngle.XYZで使用すると想定外の方向に回転してしまう
  Delphi XE10.2 Tokyoの場合、
    RotationAngle.X:=- 30に設定しても、
    RotationAngle.X:=+330に変わっている場合があるのが原因!!!
  自力でソースを記述する
}
  if (FState<>state) then
  begin
if(FState=T3DState.Jump)and((FNowAct.Step+0.1)<(FJump[length(FJump)-1].Step)) then
    begin
      FNowAct.Step:=FNowAct.Step+0.1;
    end
    else
    begin
      FOldAct:=FNowAct;
      FNowAct.Step:=0;
      FState:=state;
      FOldAct.Step:=0;
    end;
  end
  else
  begin
    if Stride<>0 then
      FNowAct.Step:=FNowAct.Step+Abs(Stride/2)
    else
      FNowAct.Step:=FNowAct.Step+0.1;
  end;

  if FState=T3DState.Walk then
    PAct:=@FWalk
  else if FState=T3DState.Jump then
    PAct:=@FJump
  else
    PAct:=@FNormal;

  FNowAct.Angle:=FNowAct.Angle+RelativeAngle;
  FNowAct.x:=FNowAct.x+cos((FNowAct.Angle-90)/180*PI)*Stride;
  FNowAct.z:=FNowAct.z-sin((FNowAct.Angle-90)/180*PI)*Stride;

  l:=Length(PAct^);
  if FNowAct.Step<=((PAct^)[0].Step) then
  begin
    rate:=FNowAct.Step/(PAct^)[0].Step;
    deg:=FOldAct.LegLN*(1-rate)+(PAct^)[0].LegLN*rate;
    FNowAct.LegLN:=deg;
    deg:=FOldAct.LegRN*(1-rate)+(PAct^)[0].LegRN*rate;
    FNowAct.LegRN:=deg;
    deg:=FOldAct.LegLT*(1-rate)+(PAct^)[0].LegLT*rate;
    FNowAct.LegLT:=deg;
    deg:=FOldAct.LegRT*(1-rate)+(PAct^)[0].LegRT*rate;
    FNowAct.LegRT:=deg;
    deg:=FOldAct.ArmLE*(1-rate)+(PAct^)[0].ArmLE*rate;
    FNowAct.ArmLE:=deg;
    deg:=FOldAct.ArmRE*(1-rate)+(PAct^)[0].ArmRE*rate;
    FNowAct.ArmRE:=deg;
    deg:=FOldAct.ArmLH*(1-rate)+(PAct^)[0].ArmLH*rate;
    FNowAct.ArmLH:=deg;
    deg:=FOldAct.ArmRH*(1-rate)+(PAct^)[0].ArmRH*rate;
    FNowAct.ArmRH:=deg;
    deg:=FOldAct.BodyY*(1-rate)+(PAct^)[0].BodyY*rate;
    FNowAct.BodyY:=deg;
  end
  else
  begin
    if FNowAct.Step>(PAct^)[l-1].Step then
    begin
      if FState=T3DState.Normal then
        FNowAct.Step:=(PAct^)[0].Step
      else
      begin
        if FState<>T3DState.Walk then
        begin
          FState:=T3DState.Normal;
          FNowAct.Step:=0;
        end
        else
        begin
          FNowAct.Step:=(PAct^)[0].Step;
        end;
      end;
    end
    else
    begin
      i:=0;
      while i<(l-1) do
      begin
        if (FNowAct.Step>=(PAct^)[i].Step) and (FNowAct.Step<=(PAct^)[i+1].Step) then
          break;
        inc(i);
      end;
      rate:=(FNowAct.Step-(PAct^)[i].Step)/((PAct^)[i+1].Step-(PAct^)[i].Step);
      deg:=(PAct^)[i].LegLN*(1-rate)+(PAct^)[i+1].LegLN*rate;
      FNowAct.LegLN:=deg;
      deg:=(PAct^)[i].LegRN*(1-rate)+(PAct^)[i+1].LegRN*rate;
      FNowAct.LegRN:=deg;
      deg:=(PAct^)[i].LegLT*(1-rate)+(PAct^)[i+1].LegLT*rate;
      FNowAct.LegLT:=deg;
      deg:=(PAct^)[i].LegRT*(1-rate)+(PAct^)[i+1].LegRT*rate;
      FNowAct.LegRT:=deg;
      deg:=(PAct^)[i].ArmLE*(1-rate)+(PAct^)[i+1].ArmLE*rate;
      FNowAct.ArmLE:=deg;
      deg:=(PAct^)[i].ArmRE*(1-rate)+(PAct^)[i+1].ArmRE*rate;
      FNowAct.ArmRE:=deg;
      deg:=(PAct^)[i].ArmLH*(1-rate)+(PAct^)[i+1].ArmLH*rate;
      FNowAct.ArmLH:=deg;
      deg:=(PAct^)[i].ArmRH*(1-rate)+(PAct^)[i+1].ArmRH*rate;
      FNowAct.ArmRH:=deg;
      deg:=(PAct^)[i].BodyY*(1-rate)+(PAct^)[i+1].BodyY*rate;
      FNowAct.BodyY:=deg;
    end;
  end;
  SetAction();
end;

constructor TBase3D.Create(parent: TFMXObject);
var PointsStr,TriangleIndicesStr:String;
    BodyStandY:Single;
begin
  FDir:=0;
  FState:=T3DState.Normal;
  FParent:=parent;

  FMate:=TLightMaterialSource.Create(nil);
  FMate.Parent:=nil;
  FMate.Ambient:=$FF303030;
  FMate.Diffuse:=$FFC0C0C0;
  FMate.Specular:=$FF606060;
  FMate.Shininess:=40;

  FDBase:=TDummy.Create(nil);
  FDBase.Parent:=FParent;

  FCamera:=TCamera.Create(nil);
  FCamera.Parent:=FDBase;
  FCamera.Position.X:=0;
  FCamera.Position.Y:=-9;
  FCamera.Position.Z:=-16;
  FCamera.RotationAngle.X:=-20;

  //位置を示すためのディスク(円)を表示する場合
  FDisk:=TDisk.Create(nil);
  FDisk.Parent:=FDBase;
  FDisk.Width:=4;
  FDisk.Depth:=4;
  FDisk.Opacity:=0;//プリミティブ自体は表示しない
  FDisk.OnRender:=OriginalRender;//独自関数で表示させる

  //■点の座標
  //X:→方向 Y:↓方向 Z:奥行き方向 で指定
  PointsStr:=
    '-1 -1 1 ,1 -1 1 ,-1 1 1 ,1 1 1   ,-1 -1 -1 ,1 -1 -1 ,-1 1 -1 ,1 1 -1';
  //0左上奥  1右上奥 2左下奥 3右下奥  4左上前   5右上前  6左下前  7右下前

  //■3角形ポリゴンを構成する3点を指定し、ポリゴン12個で構成する立方体メッシュ
  //  奥 前
  //  01 45
  //  23 67
  //  奥面     前面     左面     右面     上面     下面
  //  10 103   45 456   04 042   51 517   01 014   67 672
  //  32 302   67 657   26 246   73 713   45 415   23 273
  TriangleIndicesStr:=
    '1 0 3, 3 0 2,   4 5 6, 6 5 7,   0 4 2, 2 4 6, '+
    '5 1 7, 7 1 3,   0 1 4, 4 1 5,   6 7 2, 2 7 3';

  FBody:=TMesh.Create(nil);
  FBody.Parent:=FDBase;
  //点座標を指定
  FBody.Data.Points:=PointsStr;
  //参考:テクスチャを貼る場合(各点座標へのテクスチャのXY座標割当)
  //FBody.Data.TexCoordinates:=
  //  '1 0   ,0 0   ,1 1   ,0 1     ,0 0   ,1 0   ,0 1   ,1 1';
  //   左上奥 右上奥 左下奥 右下奥   左上前 右上前 左下前 右下前

  //3角形12個で構成される立方体メッシュを指定
  FBody.Data.TriangleIndices:=TriangleIndicesStr;
  //法線ベクトルの自動計算
  FBody.Data.CalcSmoothNormals();
  //TMeshWrapMode.Fit
  //  TMeshのWidth,Height,Depthの中に収まるようにフィット
  //  比率は保たれ、原点は自動計算される
  //TMeshWrapMode.Original
  //  TMeshのWidth,Height,Depth倍された大きさになり、原点は0,0,0
  //TMeshWrapMode.Resize
  //  基本はFitと同じだが原点は0,0,0
  //TMeshWrapMode.Stretch
  //  TMeshのWidth,Height,Depthの中に収まるようにストレッチされ
  //  比率は保たれない、原点は自動計算される
  FBody.WrapMode:=TMeshWrapMode.Original;
  FBody.Height:=1;
  FBody.Width:=1;
  FBody.Depth:=0.8;
  FBody.HitTest:=False;
  FBody.MaterialSource:=FMate;
  BodyStandY:=-1-0.6*2-0.8*2;  //FBody.Yの立っている時の値
  FBody.Position.Y:=BodyStandY;

  FHead:=TMesh.Create(nil);
  FHead.Parent:=FBody;
  FHead.Data.Points:=PointsStr;
  FHead.Data.TriangleIndices:=TriangleIndicesStr;
  FHead.Data.CalcSmoothNormals();
  FHead.WrapMode:=TMeshWrapMode.Original;
  FHead.Height:=0.4;
  FHead.Width:=0.4;
  FHead.Depth:=0.4;
  FHead.HitTest:=False;
  FHead.MaterialSource:=FMate;
  FHead.Position.Y:=-1.5;

  FDArmLE:=TDummy.Create(nil);//左肩
  FDArmLE.Parent:=FBody;
  FDArmLE.Position.X:=-1.3;
  FDArmLE.Position.Y:=-0.8;
  FDArmLE.RotationAngle.Z:=8;//左方向が正
  FDArmLE.RotationAngle.X:=0;//上方向が正に向く

  FArmLE:=TMesh.Create(nil);
  FArmLE.Parent:=FDArmLE;
  FArmLE.Data.Points:=PointsStr;
  FArmLE.Data.TriangleIndices:=TriangleIndicesStr;
  FArmLE.Data.CalcSmoothNormals();
  FArmLE.WrapMode:=TMeshWrapMode.Original;
  FArmLE.Height:=0.6;
  FArmLE.Width:=0.3;
  FArmLE.Depth:=0.3;
  FArmLE.HitTest:=False;
  FArmLE.MaterialSource:=FMate;
  FArmLE.Position.Y:=0.6;

  FDArmLH:=TDummy.Create(nil);//左ひじ
  FDArmLH.Parent:=FArmLE;
  FDArmLH.Position.Y:=0.6;
  FDArmLH.RotationAngle.X:=30;//上方向が正

  FArmLH:=TMesh.Create(nil);
  FArmLH.Parent:=FDArmLH;
  FArmLH.Data.Points:=PointsStr;
  FArmLH.Data.TriangleIndices:=TriangleIndicesStr;
  FArmLH.Data.CalcSmoothNormals();
  FArmLH.WrapMode:=TMeshWrapMode.Original;
  FArmLH.Height:=0.6;
  FArmLH.Width:=0.3;
  FArmLH.Depth:=0.3;
  FArmLH.HitTest:=False;
  FArmLH.MaterialSource:=FMate;
  FArmLH.Position.Y:=0.6;

  FDArmRE:=TDummy.Create(nil);//右肩
  FDArmRE.Parent:=FBody;
  FDArmRE.Position.X:=1.3;
  FDArmRE.Position.Y:=-0.8;
  FDArmRE.RotationAngle.Z:=-8;//左方向が正
  FDArmRE.RotationAngle.X:=-0;//上方向が正に向く

  FArmRE:=TMesh.Create(nil);
  FArmRE.Parent:=FDArmRE;
  FArmRE.Data.Points:=PointsStr;
  FArmRE.Data.TriangleIndices:=TriangleIndicesStr;
  FArmRE.Data.CalcSmoothNormals();
  FArmRE.WrapMode:=TMeshWrapMode.Original;
  FArmRE.Height:=0.6;
  FArmRE.Width:=0.3;
  FArmRE.Depth:=0.3;
  FArmRE.HitTest:=False;
  FArmRE.MaterialSource:=FMate;
  FArmRE.Position.Y:=0.6;

  FDArmRH:=TDummy.Create(nil);//右ひじ
  FDArmRH.Parent:=FArmRE;
  FDArmRH.Position.Y:=0.6;
  FDArmRH.RotationAngle.X:=30;//上方向が正

  FArmRH:=TMesh.Create(nil);
  FArmRH.Parent:=FDArmRH;
  FArmRH.Data.Points:=PointsStr;
  FArmRH.Data.TriangleIndices:=TriangleIndicesStr;
  FArmRH.Data.CalcSmoothNormals();
  FArmRH.WrapMode:=TMeshWrapMode.Original;
  FArmRH.Height:=0.6;
  FArmRH.Width:=0.3;
  FArmRH.Depth:=0.3;
  FArmRH.HitTest:=False;
  FArmRH.MaterialSource:=FMate;
  FArmRH.Position.Y:=0.6;


  FDLegLN:=TDummy.Create(nil);
  FDLegLN.Parent:=FBody;
  FDLegLN.Position.X:=-0.5;
  FDLegLN.Position.Y:=1.0;
  FDLegLN.RotationAngle.X:=-0;
  FDLegLN.RotationAngle.Z:=8;//外(左)に

  FLegLN:=TMesh.Create(nil);
  FLegLN.Parent:=FDLegLN;
  FLegLN.Data.Points:=PointsStr;
  FLegLN.Data.TriangleIndices:=TriangleIndicesStr;
  FLegLN.Data.CalcSmoothNormals();
  FLegLN.WrapMode:=TMeshWrapMode.Original;
  FLegLN.Height:=0.6;
  FLegLN.Width:=0.4;
  FLegLN.Depth:=0.4;
  FLegLN.HitTest:=False;
  FLegLN.MaterialSource:=FMate;
  FLegLN.Position.Y:=0.6;

  FDLegLT:=TDummy.Create(nil);
  FDLegLT.Parent:=FLegLN;
  FDLegLT.Position.X:=0;
  FDLegLT.Position.Y:=0.6;
  FDLegLT.RotationAngle.X:=-0; //マイナス方向のみ

  FLegLT:=TMesh.Create(nil);
  FLegLT.Parent:=FDLegLT;
  FLegLT.Data.Points:=PointsStr;
  FLegLT.Data.TriangleIndices:=TriangleIndicesStr;
  FLegLT.Data.CalcSmoothNormals();
  FLegLT.WrapMode:=TMeshWrapMode.Original;
  FLegLT.Height:=0.8;
  FLegLT.Width:=0.4;
  FLegLT.Depth:=0.4;
  FLegLT.HitTest:=False;
  FLegLT.MaterialSource:=FMate;
  FLegLT.Position.Y:=0.8;


  FDLegRN:=TDummy.Create(nil);
  FDLegRN.Parent:=FBody;
  FDLegRN.Position.X:=0.5;
  FDLegRN.Position.Y:=1.0;
  FDLegRN.RotationAngle.X:=0;
  FDLegRN.RotationAngle.Z:=-8;//外(右)に

  FLegRN:=TMesh.Create(nil);
  FLegRN.Parent:=FDLegRN;
  FLegRN.Data.Points:=PointsStr;
  FLegRN.Data.TriangleIndices:=TriangleIndicesStr;
  FLegRN.Data.CalcSmoothNormals();
  FLegRN.WrapMode:=TMeshWrapMode.Original;
  FLegRN.Height:=0.6;
  FLegRN.Width:=0.4;
  FLegRN.Depth:=0.4;
  FLegRN.HitTest:=False;
  FLegRN.MaterialSource:=FMate;
  FLegRN.Position.Y:=0.6;

  FDLegRT:=TDummy.Create(nil);
  FDLegRT.Parent:=FLegRN;
  FDLegRT.Position.X:=0;
  FDLegRT.Position.Y:=0.6;
  FDLegRT.RotationAngle.X:=-0; //マイナス方向のみ

  FLegRT:=TMesh.Create(nil);
  FLegRT.Parent:=FDLegRT;
  FLegRT.Data.Points:=PointsStr;
  FLegRT.Data.TriangleIndices:=TriangleIndicesStr;
  FLegRT.Data.CalcSmoothNormals();
  FLegRT.WrapMode:=TMeshWrapMode.Original;
  FLegRT.Height:=0.8;
  FLegRT.Width:=0.4;
  FLegRT.Depth:=0.4;
  FLegRT.HitTest:=False;
  FLegRT.MaterialSource:=FMate;
  FLegRT.Position.Y:=0.8;

  SetLength(FNormal,1);
  FNormal[0].Step:=0.5;
  FNormal[0].LegLN:=0;
  FNormal[0].LegRN:=0;
  FNormal[0].LegLT:=0;
  FNormal[0].LegRT:=0;
  FNormal[0].ArmLE:=0;
  FNormal[0].ArmRE:=0;
  FNormal[0].ArmLH:=0;
  FNormal[0].ArmRH:=0;
  FNormal[0].BodyY:=BodyStandY;

  SetLength(FWalk,5);
  FWalk[0]:=FNormal[0];
  FWalk[0].Step:=0.001;

  FWalk[1].Step:=0.5;
  FWalk[1].LegLN:=-30;
  FWalk[1].LegRN:=30;
  FWalk[1].LegLT:=0;
  FWalk[1].LegRT:=-30;
  FWalk[1].ArmLE:=30;
  FWalk[1].ArmRE:=-30;
  FWalk[1].ArmLH:=60;
  FWalk[1].ArmRH:=20;
  FWalk[1].BodyY:=BodyStandY+0.1;

  FWalk[2]:=FNormal[0];
  FWalk[2].Step:=1.0;

  FWalk[3].Step:=1.5;
  FWalk[3].LegLN:=30;
  FWalk[3].LegRN:=-30;
  FWalk[3].LegLT:=-30;
  FWalk[3].LegRT:=0;
  FWalk[3].ArmLE:=-30;
  FWalk[3].ArmRE:=30;
  FWalk[3].ArmLH:=20;
  FWalk[3].ArmRH:=60;
  FWalk[3].BodyY:=BodyStandY+0.1;

  FWalk[4]:=FNormal[0];
  FWalk[4].Step:=2.0;

  SetLength(FJump,5);
  FJump[0].Step:=0.4;
  FJump[0].LegLN:=45;
  FJump[0].LegRN:=45;
  FJump[0].LegLT:=-90;
  FJump[0].LegRT:=-90;
  FJump[0].ArmLE:=45;
  FJump[0].ArmRE:=45;
  FJump[0].ArmLH:=110;
  FJump[0].ArmRH:=110;
  FJump[0].BodyY:=BodyStandY+0.6;

  FJump[1]:=FNormal[0];
  FJump[1].Step:=FJump[0].Step+0.3;
  FJump[1].BodyY:=BodyStandY;

  FJump[2]:=FNormal[0];
  FJump[2].Step:=FJump[1].Step+0.8;
  FJump[2].BodyY:=BodyStandY-2;

  FJump[3]:=FNormal[0];
  FJump[3].Step:=FJump[2].Step+0.8;
  FJump[3].BodyY:=BodyStandY;

  FJump[4]:=FJump[0];
  FJump[4].Step:=FJump[3].Step+0.3;


  FNowAct.Step:=0;
  FNowAct.LegLN:=0;
  FNowAct.LegRN:=0;
  FNowAct.LegLT:=0;
  FNowAct.LegRT:=0;
  FNowAct.ArmLE:=0;
  FNowAct.ArmRE:=0;
  FNowAct.ArmLH:=0;
  FNowAct.ArmRH:=0;
  FNowAct.BodyY:=BodyStandY;//FBodyのY
  FNowAct.x:=0;//xz平面上の座標
  FNowAct.z:=0;//xz平面上の座標
  FNowAct.Angle:=0;

  FOldAct:=FNowAct;
end;

destructor TBase3D.Destroy;
begin
  FLegLT.Free;
  FLegRT.Free;
  FDLegLT.Free;
  FDLegRT.Free;
  FLegLN.Free;
  FLegRN.Free;
  FDLegLN.Free;
  FDLegRN.Free;

  FArmLH.Free;
  FArmRH.Free;
  FDArmLH.Free;
  FDArmRH.Free;
  FArmLE.Free;
  FArmRE.Free;
  FDArmLE.Free;
  FDArmRE.Free;

  FHead.Free;
  FBody.Free;

  FDisk.Free;

  FMate.Free;
  FCamera.Free;
  FDBase.Free;
end;


procedure TBase3D.OriginalRender(Sender: TObject; Context: TContext3D);
var i:Integer;
    col:TColorMaterialSource;
begin
  Context.BeginScene;
{
  Context.DrawLines(
    TMesh(Sender).Data.VertexBuffer,
    TMesh(Sender).Data.IndexBuffer,
    FMate.Material, 1
  );
}
  //線を描画
  for i:=0 to 35 do
  begin
    Context.DrawLine(
      Point3D(
        cos((i+0)/18*Pi)/TMesh(Sender).Width*2,
        0,
        sin((i+0)/18*Pi)/TMesh(Sender).Depth*2
      ),
      Point3D(
        cos((i+1)/18*Pi)/TMesh(Sender).Width*2,
        0,
        sin((i+1)/18*Pi)/TMesh(Sender).Depth*2
      ),
      0.2,$FFFF0000
    );
  end;
{
  //点を描画
  Context.DrawPoints(
    TMesh(Sender).Data.VertexBuffer,
    TMesh(Sender).Data.IndexBuffer,
    FMate.Material, 1
  );
}
  Context.EndScene;
end;
procedure TBase3D.SetAction;
begin
  FDLegLN.RotationAngle.X:=FNowAct.LegLN;
  FDLegRN.RotationAngle.X:=FNowAct.LegRN;
  FDLegLT.RotationAngle.X:=FNowAct.LegLT;
  FDLegRT.RotationAngle.X:=FNowAct.LegRT;
  FDArmLE.RotationAngle.X:=FNowAct.ArmLE;
  FDArmRE.RotationAngle.X:=FNowAct.ArmRE;
  FDArmLH.RotationAngle.X:=FNowAct.ArmLH;
  FDArmRH.RotationAngle.X:=FNowAct.ArmRH;
  FBody.Position.Y:=FNowAct.BodyY;
  FDBase.RotationAngle.Y:=FNowAct.Angle;
  FDBase.Position.X:=FNowAct.x;
  FDBase.Position.Z:=FNowAct.z;
end;


initialization
begin
  //True:ハードウェアDirectXを使う、False:GDIを使う
  FMX.Types.GlobalUseDX:=True;
  //True:ソフトフェアDirectXを使用する、False:ソフトウェアDirextXを使わない
  //FMX.Types.GlobalUseDXSoftware:=True;
  //True:Direct2Dを利用する、False:GDI+を使う
  FMX.Types.GlobalUseDirect2D:=True;
  //GDI+を使う場合のみ
  //True:クリアタイプレンダリングが有効、False:グレースケールアンチエイリアス処理
  FMX.Types.GlobalUseGDIPlusClearType:=false;
  //Trueだと処理が早くなるがフォーカス効果が無効になる
  FMX.Types.GlobalDisableFocusEffect:=True;
  //GPUキャンバスを使うかどうか
  FMX.Types.GlobalUseGPUCanvas:=True;
end;

end.

Unit1のソースコードを記述する

Unit1に切り替えて、以下のソースコードを記述します。
unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.StdCtrls,
  FMX.Viewport3D, FMX.Controls.Presentation ,FMX.Objects3D,
  FMX.Controls3D, FMX.Types3D,
  Winapi.Windows
  ,Unit2;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Viewport3D1: TViewport3D;
    Switch1: TSwitch;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Switch1Switch(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Viewport3D1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure Viewport3D1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Single);
    procedure Viewport3D1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
  private
    { private 宣言 }
    HumanMesh:TBase3D;
    Dummy:TDummy;
    Camera:TCamera;
    Grid3D:TGrid3D;
    Light:TLight;

    mf:Boolean;//マウス左ボタンを押しているか
    mp:TPointF;//マウス座標
  public
    { public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Viewport3D1.Multisample:=TMultisample.None;

  HumanMesh:=TBase3d.Create(Viewport3D1);

  Grid3D:=TGrid3D.Create(self);
  Grid3D.Parent:=Viewport3D1;
  Grid3D.Width:=100;
  Grid3D.Height:=100;
  Grid3D.RotationAngle.X:=90;
  Grid3D.Frequency:=4;
  Grid3D.Marks:=8;
  Grid3D.HitTest:=False;//マウスクリックに応答しない
  Grid3D.LineColor:=$CC777777;

  Dummy:=TDummy.Create(self);
  Dummy.Parent:=Viewport3D1;

  //カメラの作成
  Camera:=TCamera.Create(self);
  Camera.Parent:=Dummy;
  Camera.Position.X:=0;
  Camera.Position.Y:=-20;
  Camera.Position.Z:=-50;
  Camera.RotationAngle.X:=-30;

  Light:=TLight.Create(self);
  Light.Parent:=Viewport3D1;
  Light.RotationAngle.X:=340;
  Light.RotationAngle.Y:=330;

  Switch1.IsChecked:=True;
  Viewport3D1.Camera:=HumanMesh.Camera;
  Viewport3D1.UsingDesignCamera:=False;

  Timer1.Interval:=33;
  Timer1.Enabled:=True;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(HumanMesh) then
    FreeAndNil(HumanMesh);
end;

procedure TForm1.Switch1Switch(Sender: TObject);
begin
  if Switch1.IsChecked then
    //メッシュに追従するカメラに切り替える
    Viewport3D1.Camera:=HumanMesh.Camera
  else
    //フツーのカメラに切り替える
    Viewport3D1.Camera:=Camera;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var k:SmallInt;   //キーボード入力用
    ra:Single;    //進行方向
    s:T3DState;   //情愛
    stride:Single;//歩行速度
begin
  ra:=0;
  s:=T3DState.Normal;
  stride:=0;

  if HumanMesh.State=T3DState.Jump then
  begin
    //跳躍中だったら操作は受け付けない
    s:=T3DState.Normal;
  end
  else
  begin
    //跳躍
    k:=GetKeyState(VK_SPACE);
    if k<0 then
    begin
      s:=T3DState.Jump;
    end
    else
    begin
      //後退
      k:=GetKeyState(VK_DOWN);
      if k<0 then
      begin
        s:=T3DState.Walk;
        stride:=-0.1;
      end;
      //前進
      k:=GetKeyState(VK_UP);
      if k<0 then
      begin
        s:=T3DState.Walk;
        stride:=0.2;
        k:=GetKeyState(VK_CONTROL);
        if k<0 then
          stride:=stride+0.2;
      end;
      //左回転
      k:=GetKeyState(VK_LEFT);
      if k<0 then
      begin
        s:=T3DState.Walk;
        ra:=ra-5;
      end;
      //右回転
      k:=GetKeyState(VK_RIGHT);
      if k<0 then
      begin
        s:=T3DState.Walk;
        ra:=ra+5;
      end;
    end;
  end;
  HumanMesh.Action(s,ra,stride);
end;

procedure TForm1.Viewport3D1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  //マウスの左ボタンを押した
  mf:=True;
  mp.X:=X;
  mp.Y:=y;
end;

procedure TForm1.Viewport3D1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin
  if mf=false then exit;
  Dummy.RotationAngle.Y:=
    Dummy.RotationAngle.Y+(mp.X-x);
  mp.x:=x;
  mp.Y:=y;
end;

procedure TForm1.Viewport3D1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  mf:=False;
end;

end.

実行する

実行ボタンを押して実行します。(デバッグ実行でもOKです。)
キーボードの上矢印↑キーで前進、左矢印←キーで左回転、右矢印→キーで左回転、スペースキーでジャンプします。
Switch1のオン・オフでカメラを切り替えることが出来ます。