(*
Copyright 2001、野田篤司、anoda@mb.infoweb.ne.jp、All right reserved.

プログラムに対する改変を加えた場合でも、加えない場合でも、ソース・コードと
バイナリーのいずれの形式における再配布や使用は、下記の条件を満たした場合にのみ
許可される。

(条件 1)
ソース・コードの再配布には、必ず上記の著作権表示と、この条件リスト、および以下の
免責事項を改変せずにファイルの最初の行に入れなければならない。

(条件 2)
バイナリ−形式での再配布には、必ず上記の著作権表示と、この条件リストおよび以下の
免責事項をドキュメント中、もしくは配布と共にアーカイブなどのパッケージに入れて、
提供しなければならない。

(免責事項)
このソフトウエアは、著作者により『現状どおり』で提供するものである。
ソフトウエアに市場性があり、『売れる製品』であるような含みがあったり、『一定の目
的に適うような製品』であるような、いかなる明示的な保証は無い。また、何らかの保証
があるような含みのある一切の保証の類からの責任も全て拒否する。
著作者は、直接的・間接的・偶発的・突発的あるいは必然的等、いかなる場合においても
いかなる損害に一切の責任は負わない。その損害には、このソフトウエアの代替となりう
るソフトウエアやサービスへの調達、効果、情報や利益の低下、商取引自体の損害を含む。
また、いかなる倫理観においても、契約に関するものであろうと無かろうと、いかなる責
務においても、不正行為があろうが無かろうと、このソフトウエアを使用することによっ
て生じるいかなる損害の、一切の責任から著作者は免れる。たとえ、それが事前に当該の
損害への可能性が示唆されていた場合においても同様である。
*)


(*

　P3Dローダ兼 人体モデル制御ユニット
　MMI SANDMAN presents!!

　このユニットは、野田篤司氏作のHumanMDLのソースコードおよび、Moonlight氏による
Delphi版HumanMDLプレーヤのソースコードを一部参考にして作成されています。
　このユニットは、以上の条文のうち、「改変後のソースコードの再配布」という形態で
野田篤司氏より再配布の許可を頂いてあります。

　このユニットを使用した作品を作る際には、QuadrupleDのドキュメントにある通り、使
用者の権利は制限されることはありません(商用/非商用問わず、使用者の製作するアプリ
ケーションへの無許可での組み込みなどが可能です)が、移植者であるSANDMANおよび野田
篤司氏はなんの責任を負う義務も持たないものとします。

　このユニットの再配布(コンパイル後の.dcuのみであっても同様です)に際しては改変し
た/しないに関わらず、上記、野田氏のライセンスに従ってください。
*)

unit P3DLoader;




//P3Dローダ

interface

uses
  SysUtils, Forms, DGCarad8, d3d8, s_MathPack, classes, SXLib8, ROKLoader, QDArc;


type

//P3Dヘッダ
TP3DHeader = record
  Signature:String;   //バージョン(本モジュールでは未使用)
  ROKFileName:String; //読み込むべきROKファイル

  Optimize:Boolean;   //最適化フラグ(本モジュールでは未使用)
  Erase3Face:Boolean; //三面消去フラグ(本モジュールでは未使用)
  Smoothing:Boolean;   //スムージングフラグ
  SmoothingAngle:Single;  //スムージング角度

  Origin:D3Dvector;   //原点(本モジュールでは未使用)
  PartCount:Integer;      //パーツ数
  ScaleFactor:Single; //スケールファクタ
end;




{
0　　　　　　　　　　　　　　　　　　　 // グループ番号
2 -1 -1 -1　　　　　　　　　　　　　　　// パーツ番号（常に4個、-1は該当無しの意味）
A　　　　　　　　　　　　　　　　　　　 // 対称パーツフラグ（A、L、R）（使われていないかも？）
-0.400000 -0.100000 1.900000　　　　　　// 標準姿勢の関節位置のXYZオフセット
0.996195 0.000000 0.000000 0.087156　　 // 標準姿勢の関節の回転角初期値（クォータニオン表記）
-1 -1 腰　　　　　　　　　　　　　　　　// 親グループ番号、対称グループ番号 グループ名（-1は該当無しの意味）
}

//パーツ情報
TP3DPart = record
  ID:Integer; //パーツID
  GroupIndex:Array[0..3] of Integer;  //形状データとして使用するROKファイル内のグループ番号
  GroupCount:Integer;                 //形状データとして使用するROKファイル内のグループ数(↑から生成)
  SymmetryStatus:Integer;         //対称性についてのフラグ
  Position:D3DVector;             //標準姿勢での位置
  Orientation:TQuaternion;        //標準姿勢
  ParentID:Integer;               //親パーツのID(-1で無し)
  SymmetryID:Integer;             //対称パーツのID(-1で無し)
  Name:String;                    //パーツ名
end;



//ポーズ情報
// (モーション情報の一部としてのポーズ情報の場合、Name,partCountは未定義)
TP3DPose = record
  Name:String;                        //ポーズ名
  PartCount:Integer;                  //使用するパーツの数…多分、TP3DHeaderのPartCountと同じ
  Position:D3DVector;                 //親無しパーツの位置
  Orientations:Array of TQuaternion;  //各パーツの姿勢
end;

{
歩　　　　　　　　　　　　　　　　　　　// モーション名
16　　　　　　　　　　　　　　　　　　　// グループ数
4　　　　　　　　　　　　　　　　　　　 // フレーム数
TRUE　　　　　　　　　　　　　　　　　　// 繰り返しフラグ（補間方法切替）
1.500000　　　　　　　　　　　　　　　　// 周期（秒）
{pause definition * パーツ数 * フレーム数} // パーツ数×フレーム数分のポーズ定義

//モーション情報
TP3DMotion = record
  Name:String;  //モーション名
  PartCount:Integer;        //使用するパーツの数…多分、TP3DHeaderのPartCountと同じ
  FrameCount:Integer;       //キーフレーム数
  RepeatFlag:Boolean;       //繰り返しフラグ(?)
  Interval:Single;          //周期[秒]
  Poses:Array of TP3DPose;  //各キーフレーム毎のポーズ

  DFTp:Array of D3DVector;  //モーションのDFT係数(時間軸にキーフレーム数を取って、各キーフレームにおける位置

  //DTFq[i,j]...DTFq[i,x]はi番目のパーツのDFT係数
  DFTq:Array of Array of TQuaternion;

  fDFTq:Array of Boolean;            //姿勢について、最初のフレームと最後のフレームで内積がゼロ以上である(DFTの方法を少し変える必要アリ)
end;


TP3DFigure = class;
TP3D = class;

//補間方法…            IDFT、３次Bスプライン、線形補間、パタパタアニメ
TP3DInterpolateMethod = (pimIDFT, pimBSpline, pimLinear, pimSkip);


//P3DデータとROKのメッシュを参照しつつ動くフィギュアの操作クラス
TP3DFigure = class
  private
    P3DInfo:TP3D;             //このP3Dを元にして動きます
    FTopFrame:TSXFrame;       //このフィギュアのトップノードになるフレーム
    FPartFrames:TSXFrameList; //各パーツに対応したフレーム(TopFrameの下を辿ると、この中のいづれかに行き着く)

    //位置・姿勢計算用バッファ
    PosBuffer:Array[0..1] of D3DVector;
    QuatBuffer:Array[0..1] of Array of TQuaternion;
    //モーションiMotionの、時刻timeにおける位置・姿勢の計算、計算結果をiBufのバッファに代入
    procedure CalcPQ(iMotion:Integer; time:Single; iBuf:Integer);
    procedure SetPQ(iMotion,iBuf:Integer);  //iBufで示されるバッファの位置・姿勢をフレームにセット

    procedure ReverseY(pos:D3Dvector);                                  //HumanMDLモデルは大概Z-の方向を向いてるので、その補正をする。posは親無しフレームの位置
  public
    InterPolateMethod:TP3DInterpolateMethod;            //補間方法
    destructor Destroy; override;
    procedure StandardPose;                              //標準の姿勢を取る
    procedure Pose(poseIndex:Integer);                  //ホーズ番号で指定されたポーズを取る
    procedure Move(motionIndex:Integer; time:Single);   //モーション再生
    procedure MoveBlended(motionA,motionB:Integer; timeA,timeB:Single; rate_b:Single); //モーションのブレンド
    property TopFrame:TSXFrame read FTopFrame;
    property PartFrames:TSXFrameList read FPartFrames;
    property P3D:TP3D read P3DInfo;
end;

//P3Dファイル読み込みクラス
TP3D = class
  private
    FHeader:TP3DHeader;            //ヘッダ
    FParts:Array of TP3DPart;      //パーツ
    FMotions:Array of TP3DMotion;  //モーション
    FPoses:Array of TP3DPose;      //ポーズ

    FMotionNames:TStrings;          //モーション名とモーション番号の対応表
    FPartNames:TStrings;            //パーツ名とモーション番号の対応表
    FPoseNames:TStrings;            //ポーズ名とモーション番号の対応表

    procedure Clear;
    function GetMotions(idx: Integer): TP3DMotion;
    function GetParts(idx: Integer): TP3Dpart;
    function GetPoses(idx: Integer): TP3DPose;        //ヘッダとかパーツとかの情報を全部クリア

  public
    constructor Create;
    destructor Destroy; override;
    procedure LoadFromStream(source:TStream);
    procedure LoadFromFile(fileName:String; qdaName:String = '');
    //procedure SetBeta(ROKMeshes:TSXMeshList);                                 //メッシュにワンスキン用β値を代入する
    function Spawn(ROKMeshes:TSXMeshList; ParentFrame:TSXFrame):TP3DFigure;   //フィギュアの生成
    property P3DHeader:TP3DHeader read FHeader;
    property Motions[idx:Integer]:TP3DMotion read GetMotions;
    property MotionNames:TStrings read FMotionNames;
    property Poses[idx:Integer]:TP3DPose read GetPoses;
    property PoseNames:TStrings read FPoseNames;
    property Parts[idx:Integer]:TP3Dpart read GetParts;
    property PartNames:TStrings read FPartNames;
end;

//procedure LoadP3D(source:TStream;


implementation

{ TP3D }

//コンストラクタ
constructor TP3D.Create;
begin
  FMotionNames:=TStringList.Create;
  FPartNames:=TStringList.Create;
  FPoseNames:=TStringList.Create;
end;

procedure TP3D.Clear;
var
  i,j:Integer;
begin

  //パーツデータの解放
  FParts:=Nil;

  //ポーズデータの解放
  FPoses:=Nil;

  //モーションデータの解放
  if FMotions<>Nil then begin
    For i:=0 to High(FMotions) do begin

      //モーション由来のポーズの解放
      For j:=0 to High(FMotions[i].Poses) do begin
        FMotions[i].Poses[j].Orientations:=Nil;
      end;
      FMotions[i].Poses:=Nil;

      //DFT係数とかの解放
      FMotions[i].fDFTq:=Nil;
      FMotions[i].DFTp:=Nil;
      For j:=0 to FMotions[i].PartCount-1 do
        FMotions[i].DFTq[j]:=Nil;
      FMotions[i].DFTq:=Nil;
    end;
  end;

  FMotions:=Nil;

  FMotionNames.Clear;
  FPartNames.Clear;
  FPoseNames.Clear;
end;

//デストラクタ
destructor TP3D.Destroy;
begin
  Clear;

  FMotionNames.Free;
  FPartNames.Free;
  FPoseNames.Free;

  inherited;
end;

//動的配列の型宣言
type TSingleArray = Array of Single;


//読み込み
procedure TP3D.LoadFromStream(source: TStream);
var
  i,j,k:Integer;    //ループカウンタ
  src:TStrings;     //読み込み用
  line:Integer;     //現在読んでいる行
  spcPos:Integer;   //行分解用
  synStat:String;   //対称性フラグ

  iMotion:Integer;  //モーションのインデクス
  nPose:Integer;    //ホーズの個数
  nPart:Integer;    //ポーズの定義に使われるパーツの個数
  iPose:Integer;

  t,f:TSingleArray; //DFT係数計算用
  tmpQ:Array of Array of TQuaternion;  //作業用クォータニオン
  dp:Single;        //内積

  //DFT係数の計算
  procedure DFT(t:TSingleArray; var f:TSingleArray);
  var
    j,k,n:Integer;
    omega:Extended;
  begin
    n:=Length(t);

    For k:=0 to n-1 do
      f[k]:=0;

    For j:=0 to n-1 do begin
      For k:=0 to (n div 2)-1 do begin
        omega:= 2.0 * Pi * j*k / n;
        f[k          ]:=f[k          ] + (2.0 * t[j] * Cos(omega) / n);
        f[k + n div 2]:=f[k + n div 2] + (2.0 * t[j] * Sin(omega) / n);
      end;
      f[n div 2]:=f[n div 2] + (2.0 * t[j] * Cos(Pi * j) / n);
    end;

    if n >= 2 then
      f[n div 2]:=f[n div 2] / 2.0;
  end;

  //DFT係数の計算 その2
  procedure DFT2(t:TSingleArray; var f:TSingleArray);
  var
    j,k,n:Integer;
    omega:Extended;
  begin
    n:=Length(t);

    For k:=0 to n-1 do
      f[k]:=0;

    For j:=0 to n-1 do begin
      For k:=0 to (n div 2)-1 do begin
        omega:= Pi * (j*(2*k+1)) / n;
        f[k          ]:=f[k          ] + (4.0 * t[j] * Cos(omega) / n);
        f[k + n div 2]:=f[k + n div 2] + (4.0 * t[j] * Sin(omega) / n);
      end;
    end;
  end;


  //値をほじる関数色々
  function GetInt:Integer;
  begin
    result:=StrToInt(Trim(src[line]));
    Inc(line);
  end;

  function GetSingle:Single;
  begin
    result:=StrToFloat(Trim(src[line]));
    Inc(line);
  end;

  function GetStr:String;
  begin
    result:=src[line];
    inc(line);
  end;

  function GetBoolean:Boolean;
  begin
    result:=AnsiUpperCase(Trim(src[line])) = 'TRUE';
    inc(line);
  end;

  function GetQuaternion:TQuaternion;
  begin

    //六角のモデルを上下反転させなかったときの正解
    {
    result.w:=GetSingle;
    result.z:=-GetSingle;
    result.x:=GetSingle;
    result.y:=-GetSingle;
    }

    //result.w:=GetSingle;
    //result.z:=GetSingle;
    //result.x:=-GetSingle;
    //result.y:=-GetSingle;
    result.w:=GetSingle;
    result.z:=GetSingle;
    result.x:=-GetSingle;
    result.y:=-GetSingle;
  end;

  function GetVector:D3DVector;
  begin
    result.z:=-GetSingle;
    result.x:=GetSingle;
    result.y:=GetSingle;

    //六角のモデルを上下反転させなかったときの正解
    {
    result.z:=GetSingle;
    result.x:=GetSingle;
    result.y:=-GetSingle;
    }
  end;

begin
  //読み込み済みデータをまず、全部消す
  Clear;

  src:=TStringList.Create;
  src.LoadFromStream(source);

  PartNames.Clear;
  MotionNames.Clear;
  PoseNames.Clear;

  { スペースを改行に変換する }
  line:=0;
  while line < src.Count do begin
    src[line]:=Trim(src[line]);

    repeat
      spcPos:=Pos(' ', src[line]);
      if spcPos <> 0 then begin
        src.Insert(line, Copy(src[line], 1, spcPos-1));
        Inc(line);

        src[line]:=Copy(src[line], spcPos+1, Length(src[line]));
      end;

    until spcPos = 0;

    inc(line);
  end;

  line:=0;

  //src.SaveToFile(Application.ExeName + '_re.txt');


  {ヘッダの読み込み}
  FHeader.Signature:=GetStr;
  if FHeader.Signature <> 'HML003' then begin
    DGCarad_PutDebugMessage('未対応のP3Dデータが渡されました');
    exit;
  end;

  FHeader.ROKFileName:=GetStr;
  FHeader.Optimize:=GetBoolean;
  FHeader.Erase3Face:=GetBoolean;
  FHeader.Smoothing:=GetBoolean;
  FHeader.SmoothingAngle:=GetSingle;
  FHeader.Origin:=GetVector;
  FHeader.PartCount:=GetInt;
  FHeader.ScaleFactor:=GetSingle;



  {パーツの読み込み}
  GetStr;  //ダミー('PARTS')

  SetLength(FParts, FHeader.PartCount);
  For i:=0 to FHeader.PartCount-1 do begin

    FParts[i].ID:=GetInt;

    For j:=0 to 3 do begin
      FParts[i].GroupIndex[j]:=GetInt;
    end;
    //対称性フラグ
    synStat:=GetStr;
    if synStat = 'A' then
      FParts[i].SymmetryStatus:=0
    else if synStat = 'L' then
      FParts[i].SymmetryStatus:=1
    else if synStat = 'R' then
      FParts[i].SymmetryStatus:=2;

    FParts[i].Position:=GetVector;
    FParts[i].Orientation:=GetQuaternion;
    FParts[i].ParentID:=GetInt;
    FParts[i].SymmetryID:=GetInt;
    FParts[i].Name:=GetStr;
    PartNames.Add(FParts[i].Name);

    FParts[i].GroupCount:=0;
    For j:=0 to High(FParts[i].GroupIndex) do begin
      if FParts[i].GroupIndex[j] <> -1 then
        Inc(FParts[i].GroupCount);
    end;
  end;

  GetStr;  //ダミー('HML_END');


  {モーションの読み込み}
  GetStr;  //ダミー('MOV002');

  repeat
    SetLength(FMotions, Length(FMotions)+1);  //バッファを一個増やす
    iMotion:=High(FMotions);
    FMotions[iMotion].Name:=GetStr;
    MotionNames.Add(FMotions[iMotion].Name);
    FMotions[iMotion].PartCount:=GetInt;
    FMotions[iMotion].FrameCount:=GetInt;
    FMotions[iMotion].RepeatFlag:=GetBoolean;
    FMotions[iMotion].Interval:=GetSingle;

    //ポーズの読み込み
    nPose:=FMotions[iMotion].FrameCount;
    nPart:=FMotions[iMotion].PartCount;
    SetLength(FMotions[iMotion].Poses, nPose);
    For j:=0 to nPose-1 do begin
      FMotions[iMotion].Poses[j].Position:=GetVector;
      SetLength(FMotions[iMotion].Poses[j].Orientations, nPart);
      For k:=0 to nPart-1 do begin
        FMotions[iMotion].Poses[j].Orientations[k]:=GetQuaternion;
      end;

    end;
  until src[line] = 'MOV_END';
  Inc(line);

  //モーションのDFT係数を計算する(再生時の補間用)
  For i:=0 to High(FMotions) do begin

    //まず、親無しパーツの水平移動分のDFT係数から
    SetLength(FMotions[i].DFTp, FMotions[i].FrameCount);

    SetLength(t,FMotions[i].FrameCount);
    SetLength(f,FMotions[i].FrameCount);

    //X座標のDFT係数を計算
    For j:=0 to FMotions[i].FrameCount-1 do
      t[j]:=FMotions[i].Poses[j].Position.x;
    DFT(t,f);
    For j:=0 to FMotions[i].FrameCount-1 do
      FMotions[i].DFTp[j].x:=f[j];

    //Y
    For j:=0 to FMotions[i].FrameCount-1 do
      t[j]:=FMotions[i].Poses[j].Position.y;
    DFT(t,f);
    For j:=0 to FMotions[i].FrameCount-1 do
      FMotions[i].DFTp[j].y:=f[j];

    //Z
    For j:=0 to FMotions[i].FrameCount-1 do
      t[j]:=FMotions[i].Poses[j].Position.z;
    DFT(t,f);
    For j:=0 to FMotions[i].FrameCount-1 do
      FMotions[i].DFTp[j].z:=f[j];


    //次に、各パーツのクォータニオンのDFT係数
    SetLength(FMotions[i].DFTq, FMotions[i].PartCount);

    SetLength(tmpQ, FMotions[i].PartCount);
    For j:=0 to FMotions[i].PartCount-1 do begin
      SetLength(tmpQ[j], FMotions[i].FrameCount);
      For k:=0 to FMotions[i].FrameCount-1 do begin
        tmpQ[j,k]:=FMotions[i].Poses[k].Orientations[j];
      end;
    end;

    //内積が０以上になるようにクォータニオンを整列
    SetLength(FMotions[i].fDFTq,FMotions[i].PartCount);
    For j:=0 to FMotions[i].PartCount-1 do begin
      For k:=1 to FMotions[i].FrameCount-1 do begin
        dp:=NowDotQuat(tmpQ[j,k], tmpQ[j,k-1]);
        if dp < 0 then begin
          tmpQ[j,k].x:= -tmpQ[j,k].x;
          tmpQ[j,k].y:= -tmpQ[j,k].y;
          tmpQ[j,k].z:= -tmpQ[j,k].z;
          tmpQ[j,k].w:= -tmpQ[j,k].w;
        end;
      end;
      //最初の姿勢と最後の姿勢は大きく違うなら、DFT係数を変える必要がある
      dp:=NowDotQuat(tmpQ[j,FMotions[i].FrameCount-1], tmpQ[j,0]);
      FMotions[i].fDFTq[j]:= (dp >= 0.0);
    end;



    //DFTする
    For j:=0 to FMotions[i].PartCount-1 do begin
      SetLength(FMotions[i].DFTq[j], FMotions[i].FrameCount);

      For k:=0 to FMotions[i].FrameCount-1 do begin
        t[k]:=tmpQ[j,k].x;
      end;
      if FMotions[i].fDFTq[j] then DFT(t,f) else DFT2(t,f);
      For k:=0 to FMotions[i].FrameCount-1 do
        FMotions[i].DFTq[j,k].X:=f[k];

      //Y
      For k:=0 to FMotions[i].FrameCount-1 do begin
        t[k]:=tmpQ[j,k].y;
      end;
      if FMotions[i].fDFTq[j] then DFT(t,f) else DFT2(t,f);
      For k:=0 to FMotions[i].FrameCount-1 do
        FMotions[i].DFTq[j,k].Y:=f[k];

      //Z
      For k:=0 to FMotions[i].FrameCount-1 do begin
        t[k]:=tmpQ[j,k].z;
      end;
      if FMotions[i].fDFTq[j] then DFT(t,f) else DFT2(t,f);
      For k:=0 to FMotions[i].FrameCount-1 do
        FMotions[i].DFTq[j,k].Z:=f[k];

      //W
      For k:=0 to FMotions[i].FrameCount-1 do begin
        t[k]:=tmpQ[j,k].w;
      end;
      if FMotions[i].fDFTq[j] then DFT(t,f) else DFT2(t,f);
      For k:=0 to FMotions[i].FrameCount-1 do
        FMotions[i].DFTq[j,k].W:=f[k];

    end;

    //テンポラリバッファの解放
    For j:=0 to FMotions[i].PartCount-1 do begin
      tmpQ[j]:=Nil;
    end;
    tmpQ:=Nil;


    t:=Nil;
    f:=Nil;

  end;



  //まだデータ続いてるなら、ポーズの読み込み
  if line < src.Count then begin
    if GetStr = 'HPS002' then begin
      repeat
        SetLength(FPoses, Length(FPoses)+1);
        iPose:=High(FPoses);

        FPoses[iPose].Name:=GetStr;
        FPoseNames.Add(FPoses[iPose].Name);
        FPoses[iPose].PartCount:=GetInt;
        FPoses[iPose].Position:=GetVector;
        nPart:=FHeader.PartCount;

        SetLength(FPoses[iPose].Orientations,nPart);
        For i:=0 to nPart-1 do begin
          FPoses[iPose].Orientations[i]:=GetQuaternion;
        end;
      until src[line] = 'HPS_END';

    end;
  end;

  //ここまでで、P3Dファイルの読み込みは完了
  src.Free;


end;


function TP3D.Spawn(ROKMeshes:TSXMeshList; ParentFrame:TSXFrame): TP3DFigure;
var
  i,j:Integer;
  partFrames:TSXFrameList; //パーツ格納用フレーム参照用
  newFrame:TSXFrame;
  meshCount:Integer;      //着目しているフレームに格納すべきメッシュの数
  meshFrame:TSXFrame;     //メッシュ格納用フレーム
  //mtrx1,mtrx2:D3DMATRIX;
  //pf:TSXFrame;
begin
  result:=TP3DFigure.Create;

  result.P3DInfo:=Self;

  //トップノードの作成
  result.FTopFrame:=TSXFrame.Create(ParentFrame);
  {
  with result.FTopFrame do begin
    SetTranslation(Parent, Header.Origin);  //なんか、この、原点というパラメータは実は未使用らしい
  end;
  }

  //各ノードを作っていく
  PartFrames:=TSXFrameList.Create;
  result.FPartFrames:=partFrames;
  For i:=0 to FHeader.PartCount-1 do begin
    //親フレームは後で変えるので、この時点ではどうでもいい
    newFrame:=TSXFrame.Create(Nil);
    partFrames.Add(newFrame);
    newFrame.Tag:=i;    //自分がPartFramesの何番目かをセット

    //メッシュの追加
    meshCount:=Parts[i].GroupCount;

    if meshCount = 0 then
      //0個ならなにもしない
      continue
    else if meshCount = 1 then begin
      //1個ならそのままくっつけ
      For j:=0 to High(Parts[i].GroupIndex) do begin
        if Parts[i].GroupIndex[j] <> -1 then begin
          NewFrame.Mesh:=ROKMeshes[Parts[i].GroupIndex[j]];
          NewFrame.EnableMeshTransform:=False;
          //平行移動分をMeshMatrixに設定
          //mtrx1:=NowTranslateMatrix(-Parts[i].Position.x, -Parts[i].Position.y, -Parts[i].Position.z);
          //mtrx2:=NowMatrixFromQuat(NowInvQuat(Parts[i].Orientation));
          //NewFrame.MeshMatrix:=NowCompositeMatrix(mtrx2,mtrx1);
        end;
      end;
    end else begin
      //2個以上なら、子フレームをメッシュ分作って、それぞれに格納
      For j:=0 to High(Parts[i].GroupIndex) do begin
        if Parts[i].GroupIndex[j] <> -1 then begin
          meshFrame:=TSXFrame.Create(newFrame);
          meshFrame.Mesh:=ROKMeshes[Parts[i].GroupIndex[j]];
          meshFrame.Style:=sxfsMeshContainer; //このフレームの位置・姿勢は親フレームと同一
          meshFrame.EnableMeshTransform:=False;
          //mtrx1:=NowTranslateMatrix(-Parts[i].Position.x, -Parts[i].Position.y, -Parts[i].Position.z);
          //mtrx2:=NowMatrixFromQuat(NowInvQuat(Parts[i].Orientation));
          //meshFrame.MeshMatrix:=NowCompositeMatrix(mtrx2,mtrx1);
        end;
      end;
      //NewFrame.MeshMatrix:=NowCompositeMatrix(mtrx2,mtrx1);
    end;

  end;


  //親子関係の設定
  for i:=0 to FHeader.PartCount-1 do begin
    if Parts[i].ParentID = -1 then    //-1ならTopFrame直下
      partFrames[i].Parent:=result.TopFrame
    else
      partFrames[i].Parent:=partFrames[Parts[i].ParentID];
    //partFrames[i].SetTranslation(result.TopFrame, Parts[i].Position);
  end;

  //デフォルト姿勢のMeshMatrixへの適用
  {
  for i:=0 to FHeader.PartCount-1 do begin
    pf:=partFrames[i].Parent;

    while pf <> result.TopFrame do begin
      partFrames[i].MeshMatrix:=NowCompositeMatrix(partFrames[i].MeshMatrix,NowExtractRotation(pf.MeshMatrix));
      pf:=pf.Parent;
    end;
  end;
  }



  //計算用バッファもつくってやる
  SetLength(result.QuatBuffer[0], FHeader.PartCount);
  SetLength(result.QuatBuffer[1], FHeader.PartCount);


  //位置・姿勢の設定
  result.StandardPose;

end;

function TP3D.GetMotions(idx: Integer): TP3DMotion;
begin
  result:=FMotions[idx];
end;

function TP3D.GetParts(idx: Integer): TP3Dpart;
begin
  result:=FParts[idx];
end;

function TP3D.GetPoses(idx: Integer): TP3DPose;
begin
  result:=FPoses[idx];
end;


procedure TP3D.LoadFromFile(fileName: String; qdaName:String = '');
var
  src:TStream;
begin
  if qdaName = '' then
    src:=TFileStream.Create(filename, fmOpenRead)
  else
    src:=ExtractFromQDAFile(qdaName, fileName);

  LoadFromStream(src);
  src.Free;
end;



{ TP3DFigure }

//デストラクタ
destructor TP3DFigure.Destroy;
begin
  FTopFrame.Free;   //再帰的に全子フレーム込みで解放
  FPartFrames.Free; //FrameListを解放しても、フレーム自体はFreeされないので、↑とで二度Freeにならない
  QuatBuffer[0]:=Nil;
  QuatBuffer[1]:=Nil;

  inherited;
end;

//モーション再生(2モーション混合)
procedure TP3DFigure.MoveBlended(motionA, motionB: Integer; timeA, timeB,
  rate_b: Single);
var
  i:Integer;
  nA,nB:Integer;  //モーションAとBの使用パーツ数
begin

  nA:=P3DInfo.Motions[motionA].PartCount;
  nB:=P3DInfo.Motions[motionB].PartCount;

  //各モーションについて計算
  CalcPQ(motionA, timeA,0);
  CalcPQ(motionB, timeB,1);

  //計算結果を補間
  PosBuffer[0]:=NowLerp3D(PosBuffer[0],PosBuffer[1], rate_b);

  if nA < nB then begin
    For i:=0 to nA-1 do
      QuatBuffer[0,i]:=NowLerpQuat(QuatBuffer[0,i],QuatBuffer[1,i], rate_b);
    For i:=nA to nB-1 do
      QuatBuffer[0,i]:=QuatBuffer[1,i];

    SetPQ(motionB, 0);
  end else begin
    For i:=0 to nB-1 do
      QuatBuffer[0,i]:=NowLerpQuat(QuatBuffer[0,i],QuatBuffer[1,i], rate_b);
    For i:=nB to nA-1 do
      QuatBuffer[0,i]:=QuatBuffer[0,i];

    SetPQ(motionA, 0);
  end;


end;



//モーション再生
procedure TP3DFigure.Move(motionIndex: Integer; time: Single);
begin
  CalcPQ(MotionIndex, time, 0);
  SetPQ(MotionIndex, 0);
end;

//はい、ポーズ
procedure TP3DFigure.Pose(poseIndex: Integer);
var
  i:Integer;
  mtrx:D3DMATRIX;
  q,tq:TQuaternion;
begin

  For i:=0 to P3D.Poses[poseIndex].PartCount-1 do begin
    with P3D.Parts[i] do begin
      q:=P3D.Poses[poseIndex].Orientations[i];
      tq:=q;
      tq.W:=-tq.w;
      tq.z:=-tq.z;
      mtrx:=NowCompositeMatrix(
        NowTranslateMatrix(-Position.x, -Position.y, -Position.z),
        NowMatrixFromQuat(NowMulQuat(q, Orientation)));

      {
      mtrx:=NowTranslateMatrix(-Position.x, -Position.y, -Position.z);
      mtrx:=NowCompositeMatrix(mtrx, NowMatrixFromQuat(Orientation));
      mtrx:=NowCompositeMatrix(mtrx, NowMatrixFromQuat(q));
      }


      mtrx._41:=mtrx._41 + Position.x;
      mtrx._42:=mtrx._42 + Position.y;
      mtrx._43:=mtrx._43 + Position.z;
    end;

    PartFrames[i].Matrix:=mtrx;
  end;

  //Y軸周りに180度回転
  ReverseY(P3D.Poses[poseIndex].Position);
end;

//標準の姿勢を取る
procedure TP3DFigure.StandardPose;
var
  i:Integer;
//  v:THgVector;
begin
  For i:=0 to P3D.FHeader.PartCount-1 do begin
    PartFrames[i].SetTranslation(TopFrame, Vector(0,0,0));
    //PartFrames[i].SetOrientationQuat(PartFrames[i].Parent, P3D.Parts[i].Orientation);
    PartFrames[i].SetOrientationQuat(PartFrames[i].Parent, Quaternion(1,0,0,0));
  end;

  ReverseY(Vector(0,0,0));
end;



//モーションデータから位置・姿勢を計算して、Pos/QuatBuffer[iBuf]に入れる
procedure TP3DFigure.CalcPQ(iMotion: Integer; time: Single; iBuf: Integer);
var
  i,j:Integer;
  motionT:Single;       //モーションの頭からの時刻
  frameInterval:Single; //キーフレームの間隔
  iFrame:Integer;       //現在、何番目のキーフレームまで通過したところか
  frameT:Single;        //↑のキーフレームを通過してからの時間
  r:Single;             //補間係数

  p:Array[0..3] of D3DVector; //親無しフレームの位置ベクトル補間用
  pos:D3Dvector;

  q:Array[0..3] of TQuaternion;
  quat:TQuaternion;

  phase:Extended;

  //クォータニオンのIDFT、phaseとmotionIndexを参照しつつ、指定パーツの姿勢を求める
  function IDFT(iPart:Integer):TQuaternion;
  var
    i:Integer;    //ループカウンタ
    n,hn:Integer; //フレーム数,その半分
    omega:Extended;
    c,s:Single;
  begin

    with P3D.Motions[iMotion] do begin
      n:=FrameCount;
      hn:=n div 2;

      if fDFTq[iPart] then begin
        c:=Cos(phase * hn);
        result.X:=DFTq[iPart,0].X/2.0 + DFTq[iPart, hn].X * c;
        result.Y:=DFTq[iPart,0].Y/2.0 + DFTq[iPart, hn].Y * c;
        result.Z:=DFTq[iPart,0].Z/2.0 + DFTq[iPart, hn].Z * c;
        result.W:=DFTq[iPart,0].W/2.0 + DFTq[iPart, hn].W * c;
        for i:=1 to hn-1 do begin
          omega:=phase * i;
          c:=Cos(omega);
          s:=Sin(omega);
          result.x:=result.x + (DFTq[iPart, i].X * c + DFTq[iPart, i+hn].X * s);
          result.y:=result.y + (DFTq[iPart, i].Y * c + DFTq[iPart, i+hn].Y * s);
          result.z:=result.z + (DFTq[iPart, i].Z * c + DFTq[iPart, i+hn].Z * s);
          result.w:=result.w + (DFTq[iPart, i].W * c + DFTq[iPart, i+hn].W * s);
        end;

      end else begin

        result:=Quaternion(0,0,0,0);
        for i:=0 to hn-1 do begin
          omega:=phase / 2.0 * (2*i+1);
          c:=Cos(omega);
          s:=Sin(omega);
          result.x:=result.x + (DFTq[iPart, i].X * c + DFTq[iPart, i+hn].X * s);
          result.y:=result.y + (DFTq[iPart, i].Y * c + DFTq[iPart, i+hn].Y * s);
          result.z:=result.z + (DFTq[iPart, i].Z * c + DFTq[iPart, i+hn].Z * s);
          result.w:=result.w + (DFTq[iPart, i].W * c + DFTq[iPart, i+hn].W * s);
        end;

      end;
    end;
    result:=NowNormalizeQuat(result);

  end;

  //ベクトルのIDFT
  function IDFTv:D3DVector;
  var
    i:Integer;    //ループカウンタ
    n,hn:Integer; //フレーム数,その半分
    omega:Extended;
  begin
    with P3D.Motions[iMotion] do begin
      n:=FrameCount;
      hn:=n div 2;

      result.X:=DFTp[0].X/2.0 + DFTp[hn].X * Cos(phase * hn);
      result.Y:=DFTp[0].Y/2.0 + DFTp[hn].Y * Cos(phase * hn);
      result.Z:=DFTp[0].Z/2.0 + DFTp[hn].Z * Cos(phase * hn);
      for i:=1 to hn-1 do begin
        omega:=phase * i;
        result.x:=result.x + (DFTp[i].X * Cos(omega) + DFTp[i+hn].X * Sin(omega));
        result.y:=result.y + (DFTp[i].Y * Cos(omega) + DFTp[i+hn].Y * Sin(omega));
        result.z:=result.z + (DFTp[i].Z * Cos(omega) + DFTp[i+hn].Z * Sin(omega));
      end;
    end;
  end;

begin
  motionT:=time - Trunc(time / P3D.Motions[iMotion].Interval) * P3D.Motions[iMotion].Interval;
  frameInterval:=P3D.Motions[iMotion].Interval / P3D.Motions[iMotion].FrameCount;

  iFrame:=Trunc(motionT / frameInterval);
  frameT:=motionT - iFrame*frameInterval;

  r:=frameT / frameInterval;
  phase:=motionT / P3D.Motions[iMotion].Interval * Pi * 2.0;


  //親無しフレームの位置
  Case InterPolateMethod of
    pimIDFT:
      pos:=IDFTv;
    pimBSpline: begin
      For i:=0 to 3 do
        p[i]:= P3D.Motions[iMotion].Poses[(P3D.Motions[iMotion].FrameCount + i + iFrame - 1) mod P3D.Motions[iMotion].FrameCount].Position;

      pos.x:=NowInterpolate(p[0].x, p[1].x, p[2].x, p[3].x, r);
      pos.y:=NowInterpolate(p[0].y, p[1].y, p[2].y, p[3].y, r);
      pos.z:=NowInterpolate(p[0].z, p[1].z, p[2].z, p[3].z, r);
    end;
    pimLinear: begin
      For i:=0 to 1 do
        p[i]:= P3D.Motions[iMotion].Poses[(i + iFrame) mod P3D.Motions[iMotion].FrameCount].Position;
      pos:=NowLerp3D(p[0],p[1], r);
    end;
    pimSkip: begin
      pos:=P3D.Motions[iMotion].Poses[iFrame].Position;
    end;
  end;

  PosBuffer[iBuf]:=pos;


  //各パーツの姿勢
  For i:=0 to P3D.Motions[iMotion].PartCount-1 do begin

    Case InterPolateMethod of
      pimIDFT:
        quat:=IDFT(i);
      pimBSpline: begin
        For j:=0 to 3 do
          q[j]:=
            P3D.Motions[iMotion].Poses[
              (P3D.Motions[iMotion].FrameCount + j + iFrame -1) mod P3D.Motions[iMotion].FrameCount
            ].Orientations[i];
        quat.x:=NowInterpolate(q[0].x, q[1].x, q[2].x, q[3].x, r);
        quat.y:=NowInterpolate(q[0].y, q[1].y, q[2].y, q[3].y, r);
        quat.z:=NowInterpolate(q[0].z, q[1].z, q[2].z, q[3].z, r);
        quat.w:=NowInterpolate(q[0].w, q[1].w, q[2].w, q[3].w, r);
      end;
      pimLinear: begin
        For j:=0 to 1 do
          q[j]:=NowNormalizeQuat(
            P3D.Motions[iMotion].Poses[
              (j + iFrame) mod P3D.Motions[iMotion].FrameCount
            ].Orientations[i]);
        quat:=NowSlerpQuat(q[0],q[1],r);
      end;
      pimSkip: begin
        quat:=P3D.Motions[iMotion].Poses[iFrame mod P3D.Motions[iMotion].FrameCount].Orientations[i];
      end;

    end;

    //quat.z:=-quat.z;
    //quat.w:=-quat.w;
    quat:=NowNormalizeQuat(quat);

    QuatBuffer[iBuf,i]:=quat;
  end;

end;

//Pos/QuatBuffer[iBuf]に入っている計算結果をフレームに代入
procedure TP3DFigure.SetPQ(iMotion,iBuf: Integer);
var
  i:Integer;
  pos:D3DVector;             //親なしフレームの位置(モーションの平行移動分)
  mtrx:D3DMatrix;
  {
  hv:THgvector;              //平行移動分計算用
  tmpV,v:D3Dvector;          //平行移動分計算用
  rotQ:Array of TQuaternion; //軸回転用クォータニオン

  curFrame:TSXFrame;         //注目しているフレーム
  iGeneration:Integer;       //注目しているフレームの世代
  aQ:TQuaternion;            //祖先のクォータニオン
  }
begin

  {
  //軸回転用クォータニオンの作成
  SetLength(rotQ, P3DInfo.Motions[iMotion].PartCount);


  For i:=0 to P3DInfo.Motions[iMotion].PartCount-1 do begin
    rotQ[i]:=QuatBuffer[iBuf,i];

    //TopFrameまでたぐる
    curFrame:=PartFrames[i].Parent;
    iGeneration:=PartFrames[i].Ancestors.Count-1;
    while curFrame <> TopFrame do begin
      curFrame:=curFrame.Parent;
      Dec(iGeneration);
    end;

    //TopFrameより1世代下から、軸回転用クォータニオンの作成
    Inc(iGeneration);
    For j:=iGeneration to PartFrames[i].Ancestors.Count-1 do begin
      aQ:=P3Dinfo.Parts[PartFrames[i].Ancestors[j].Tag].Orientation;
      rotQ[i]:=NowMulQuat( NowMulQuat( NowInvQuat(aQ), rotQ[i]), aQ);
    end;

  end;



  pos:=PosBuffer[iBuf];

  For i:=0 to P3DInfo.Motions[iMotion].PartCount-1 do begin

    quat:=NowMulQuat(rotQ[i], P3DInfo.Parts[i].Orientation);

    v:=P3DInfo.Parts[i].Position;
    hv:=NowHomoginize(v);
    hv:=NowTransform(hv, NowMatrixFromQuat(quat));
    v:=NowSub(P3DInfo.Parts[i].Position , NowHeteroginize(hv));

    curFrame:=PartFrames[i].Parent;
    while curFrame <> TopFrame do begin
      q:=NowMulQuat(rotQ[curFrame.Tag], P3DInfo.Parts[curFrame.Tag].Orientation);

      //平行移動分の計算
      tmpV:=P3DInfo.Parts[curFrame.Tag].Position;
      hv:=NowHomoginize(NowSub(v, tmpV));
      hv:=NowTransform(hv, NowMatrixFromQuat(q));
      v:=NowAdd( NowHeteroginize(hv), tmpV);

      //姿勢の計算
      quat:=NowMulQuat(q, quat);

      curFrame:=curFrame.Parent;
    end;

    v:=NowAdd(v,pos);

    //セット
    PartFrames[i].SetOrientationQuat(TopFrame,quat);
    PartFrames[i].SetTranslation(TopFrame, v);



    //親無しフレームの位置を足す
    {
    if P3DInfo.Parts[i].ParentID = -1 then begin
      PartFrames[i].SetTranslation(TopFrame, pos);
    end else begin
      PartFrames[i].SetTranslation(TopFrame, pos);
    end;

  end;
  }


  For i:=0 to P3D.Motions[iMotion].PartCount-1 do begin
    with P3D.Parts[i] do begin
      mtrx:=NowCompositeMatrix(
        NowTranslateMatrix(-Position.x, -Position.y, -Position.z),
//        NowMatrixFromQuat(NowMulQuat(Orientation, QuatBuffer[iBuf,i])));
        NowMatrixFromQuat(NowMulQuat(QuatBuffer[iBuf,i], Orientation)));
      mtrx._41:=mtrx._41 + Position.x;
      mtrx._42:=mtrx._42 + Position.y;
      mtrx._43:=mtrx._43 + Position.z;
    end;

    PartFrames[i].Matrix:=mtrx;
  end;

  //Y軸周りに180度回転
  ReverseY(PosBuffer[iBuf]);
  //rotQ:=Nil;
end;


//Y軸周りに反転
procedure TP3DFigure.ReverseY(pos:D3Dvector);
var
  mtrx:D3DMATRIX;
  i:Integer;
begin
  // 親無しフレームなら、平行移動分を足してから…
  //Y軸周りに180度回転(Z+の方向を向くようにする)
  For i:=0 to PartFrames.Count-1 do begin

    mtrx:=PartFrames[i].Matrix;

    with P3D.Parts[i] do begin
      if ParentID = -1 then begin
        mtrx._11:=-mtrx._11;
        mtrx._13:=-mtrx._13;

        mtrx._21:=-mtrx._21;
        mtrx._23:=-mtrx._23;

        mtrx._31:=-mtrx._31;
        mtrx._33:=-mtrx._33;

        mtrx._41:=-mtrx._41 - pos.x;  //平行移動分も、Y軸周りに反転
        mtrx._42:=mtrx._42 + pos.y;
        mtrx._43:=-mtrx._43 - pos.z;
      end;
    end;

    PartFrames[i].Matrix:=mtrx;
  end;

end;

{
//メッシュにβ値を代入
procedure TP3D.SetBeta(ROKMeshes: TSXMeshList);
var
  i,j,k:Integer;
  iPart:Integer;  //パーツ番号
  jpos:D3Dvector; //関節の位置

  nChild:Integer; //子ボーンの数
  cpos:D3Dvector; //子ボーンの付く位置

  boneLength:Single;  //ボーンの長さ
  dp:Single;          //正射影の長さ
  d,far_d:Single;     //親ボーンからの距離、親ボーンの位置からもっとも遠い点までの距離
  beta:Single;        //最終的に求められるβ値

  pV:PROKVertex;
begin

  For i:=0 to ROKMeshes.Count-1 do begin

    //このメッシュがどのパーツで使われているかを調べる
    iPart:=-1;
    For j:=0 to P3DHeader.PartCount-1 do begin
      For k:=0 to Parts[j].GroupCount-1 do begin
        if Parts[j].GroupIndex[k] = i then begin
          iPart:=j;
          break;
        end;
      end;
    end;

    //そもそも使われていないメッシュなら、何もしない
    if iPart = -1 then
      continue;

    //親無しボーンなら、β値いらない
    if Parts[iPart].ParentID = -1 then
      continue;

    //パーツの関節の位置を調べる
    jpos:=Parts[iPart].Position;


    //子ボーンの付く位置を調べる
    nChild := 0;
    cpos:=Vector(0,0,0);
    For j:=0 to P3DHeader.PartCount-1 do begin
      if Parts[j].ParentID = iPart then begin
        Inc(nChild);
        cpos:=NowAdd(cpos, Parts[j].Position);
      end;
    end;

    //子ボーンが有る場合、子ボーンの付く位置の重心を取る
    if nChild > 1 then
      cpos:=NowScale(cpos, 1/nChild);


    pV:=ROKMeshes[i].VBuf.Lock(0,0,0);


    //子ボーンが無いなら、関節位置から一番遠い点を探し、そこを子ボーンの位置と同じようにみなす
    if nChild = 0 then begin
      far_d:=0;
      cpos:=jpos;

      For j:=0 to ROKMeshes[i].VBuf.Count-1 do begin
        NowDistance(d, jpos, pV^.pos);
        if d > far_d then begin
          far_d:=d;
          cpos:=pV^.pos;
        end;
        Inc(pV);
      end;

      Dec(pV, ROKMeshes[i].VBuf.Count);
    end;


    //関節位置と子ボーンの付着点を軸にとる円筒を考えてβ値を決定
    NowDistance(boneLength, jpos,cpos);

    For j:=0 to ROKMeshes[i].VBuf.Count-1 do begin
      //円筒の軸に対して、メッシュ上の着目してる頂点の正射影をとる
      NowDotProduct(dp, NowSub(pV^.pos, jpos), NowSub(cpos, jpos));

      if boneLength = 0.0 then
        pV^.beta:=1.0
      else begin
        //影の長さをボーンの長さで正規化して、β値にする。
        //親に近ければ0.0、子に近ければ1.0を代入
        beta:=dp / boneLength;

        if beta >= 0.5 then
          beta:=1.0
        else if beta < 0.0 then
          beta:=0
        else
          beta:=beta * 2;

        pV^.beta:=beta;
        //pV^.diffuse:=NowLerpColor($FF000000, $FFFFFFFF, beta);
      end;
      Inc(pV);
    end;

    ROKMeshes[i].VBuf.Unlock;


  end;

end;
}

end.
