unit RokLoader;

//フリー版六角大王 .ROKファイルローダユニット

//Moonlight氏のコードを元に、SANDMANが改変させていただきました。
//Moonlight氏に感謝します!!


interface

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

procedure ROKLoadFromStream(DG:TDGCarad; source:TStream; meshes:TSXMeshList; smoothDegree:Single);

procedure ROKLoadFromFile(DG:TDGCarad; filename:String; meshes:TSXMeshList; smoothDegree:Single; qdaName:String = '');


const
//六角ローダ用頂点フォーマット
FVF_ROK = (D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_DIFFUSE Or D3DFVF_TEX1);


type

//六角ローダ用頂点データ

TROKVertex = packed record

  Case Integer of
    0: (
      x,y,z:Single;     //頂点
      nx,ny,nz:Single;  //法線
      diffuse:D3DCOLOR;   //ディフューズ
      tu,tv:Single;       //テクスチャ座標
      );
    1:(
      pos:D3DVector;
      normal:D3DVector;
      );
end;

PROKVertex = ^TROKVertex;


function ROKVertex(x,y,z:Single; nx,ny,nz:Single; col:D3DCOLOR; tu,tv:Single):TROKVERTEX; overload;
function ROKVertex(pos:D3DVector; normal:D3Dvector; col:D3DCOLOR; tu,tv:Single):TROKVERTEX; overload;


implementation

type

TROKPoint = record
  ID:Integer;       //点番号
  iGroup:Integer;   //所属するグループ番号
  Visible:Boolean;  //見える？
  Mirror:Integer;   //対称な点の番号(0なら2次元の点)
  Pos:D3DVector;    //座標

  Color:Integer;    //色番号(面の読み取りのときに付加される)
end;

TROKLine = record
  startPoint,endPoint:Integer;  //視点・終点のID
  iGroup:Integer;   //所属するグループの番号
  Visible:Boolean;  //見える？
end;

TROKFace = record
  ID:Integer;
  LineCount:Integer;  //面を構成する線の数
  Color:Integer;      //色番号
  Lines:PInteger;     //輪郭線番号

  Points:PInteger;    //輪郭線を構成する点のID(Linesから割り出す)
  Normal:D3DVector;   //面の法線、↑から割り出す
  GP:D3DVector;       //面の重心、↑から割り出す
  iGroup:Integer;     //グループ番号(Linesから割り出す)
end;


TROKColor = record
  r0,g0,b0:Integer;   //明るい所の色
  r1,g1,b1:Integer;   //暗い所の色
  col:D3DColor;       //↑を総合して作ったD3DCOLOR
end;


function ROKVertex(x,y,z:Single; nx,ny,nz:Single; col:D3DCOLOR; tu,tv:Single):TROKVERTEX; overload;
begin
  result.x:=x;
  result.y:=y;
  result.z:=z;
//  result.b:=beta;
  result.nx:=nx;
  result.ny:=ny;
  result.nz:=nz;
  result.diffuse:=col;
  result.tu:=tu;
  result.tv:=tv;
end;

function ROKVertex(pos:D3DVector; normal:D3Dvector; col:D3DCOLOR; tu,tv:Single):TROKVERTEX; overload;
begin
  result.pos:=pos;
//  result.beta:=beta;
  result.normal:=normal;
  result.diffuse:=col;
  result.tu:=tu;
  result.tv:=tv;
end;


procedure ROKLoadFromStream(DG:TDGCarad; source:TStream; meshes:TSXMeshList; smoothDegree:Single);
var
  g,i,j,k:Integer;
  src:TStrings;               //読み込み元を文字列リストに入れた物
  debugStr:String;
  line:Integer;               //今読んでいる行の番号

  nPoint,nLine,nFace,nColor,nGroup:Integer;
  nVertices:Array of Integer;          //頂点数
  nIndices:Array of Integer;           //頂点インデクス数
  iV,iI:Array of Integer;              //頂点・頂点インデクス

  Points:Array of TROKPoint;
  Lines:Array of TROKLine;
  Faces:Array of TROKFace;
  Colors:Array of TROKColor;

  homeFaces:Array of Array of Integer;  //頂点の属する面リスト

  p:TROKPoint;                //頂点参照用
  pV:Array of PROKVertex;      //VBuf書き込み用
  pL:PInteger;                //線インデクス配列へのポインタ
  pP,pPs,pPd:PInteger;        //点インデクス配列へのぽいんた
  nextStartP,nextEndP,startP,endP:Integer;  //点情報取得用


  normal:D3DVector; //法線計算用ベクトル
  vn:Array [0..2] of D3DVector;
  vg:D3Dvector;     //重心計算用ベクトル
  include:Boolean;  //着目している点が、面に含まれている

  phi,epsilon:Single;        //対抗角度と、纏める際の閾値
  theta:Single;

  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;

  //点情報読み取り・格納(点データが続く場合はTrueさもなくばFalse)
  function GetPoint:Boolean;
  var
    dat:TROKPoint;
  begin
    //データゲッチュ
    if src[line] = 'LINE0' then begin
      Inc(line);
      result:=False;
      exit;
    end;

    dat.ID:=GetInt;
    dat.iGroup:=GetInt;
    dat.Visible:=(GetInt = 0);
    GetInt;   //ダミー読み込み
    dat.Mirror:=GetInt;   //左右対称な点番号
    dat.Pos.x:=GetSingle;
    dat.Pos.y:=-GetSingle;  //Y軸を逆転
    dat.Pos.z:=GetSingle;

    //Points[dat.ID]にdat.ID番の点が必ず入るようにする
    if nPoint <= dat.ID then
      nPoint:=dat.ID+1;

    //配列の容量オーバーしたら、2倍に拡張
    while nPoint >= High(Points) do
      SetLength(Points, Length(Points)*2);

    //格納
    Points[dat.ID] := dat;

    result:=True;
  end;

  //線情報読み取り
  function GetLine:Boolean;
  var
    dat:TROKLine;
  begin
    //データゲッチュ
    if src[line] = 'FACE0' then begin
      Inc(line);
      result:=False;
      exit;
    end;

    dat.startPoint:=GetInt;
    dat.endPoint:=GetInt;
    dat.iGroup:=GetInt;
    dat.Visible:=(GetInt = 0);

    //グループ番号の上限を求める
    if nGroup < dat.iGroup then
      nGroup:=dat.iGroup;

    //配列の容量オーバーしたら、2倍に拡張
    if nLine = High(Lines) then
      SetLength(Lines, Length(Lines)*2);

    //格納
    Lines[nLine] := dat;

    Inc(nLine);
    result:=True;
  end;


  //面情報読み取り
  function GetFace:Boolean;
  var
    dat:TROKFace;
    i:Integer;
  begin
    //データゲッチュ
    if src[line] = 'END00' then begin
      Inc(line);
      result:=False;
      exit;
    end;

    dat.LineCount:=GetInt;
    dat.Color:=GetInt;
    dat.ID:=GetInt;

    //線情報を取得
    GetMem(dat.Lines, dat.LineCount * Sizeof(Integer));
    pL:=dat.Lines;
    For i:=0 to dat.LineCount-1 do begin
      pL^:=GetInt-1; //線番号は1がスタートだけど、Lines配列は0スタート
      Inc(pL);
    end;

    //グループ番号を得る
    pL:=dat.Lines;
    dat.iGroup:=Lines[pL^].iGroup;
    Inc(pL);
    For i:=1 to dat.LineCount-1 do begin
      if dat.iGroup <> Lines[pL^].iGroup then begin
        dat.iGroup:=-1; //一つでもグループ番号が不一致なら、-1を代入
        break;
      end;
      Inc(pL);
    end;
    // どのグループにも属さない面は弾く
    if dat.iGroup = -1 then begin
      FreeMem(dat.Lines);
      result:=True;
      exit;
    end;


    //線情報から点情報を作成
    GetMem(dat.Points, dat.LineCount * Sizeof(Integer));
    pL:=dat.Lines;
    pP:=dat.Points;

    debugStr:=Format('Face #%.5d : ', [nFace]);
    For i:=0 to dat.LineCount-1 do begin

      startP:=Lines[pL^].startPoint;
      endP:=Lines[pL^].endPoint;
      Inc(pL);


      if i = dat.LineCount-1 then begin
        nextStartP:=Lines[dat.Lines^].startPoint;
        nextEndP:=Lines[dat.Lines^].endPoint;
      end else begin
        nextStartP:=Lines[pL^].startPoint;
        nextEndP:=Lines[pL^].endPoint;
      end;

      if (startP = nextStartP) Or (startP = nextEndP) then
        pP^:=startP
      else
        pP^:=endP;

      debugStr:=debugStr + IntToStr(pP^) + ' - ';

      Inc(pP);

    end;
    //DGCarad_PutDebugMessage(debugStr);


    //配列の容量オーバーしたら、2倍に拡張
    if nFace = High(Faces) then
      SetLength(Faces, Length(Faces)*2);

    //格納
    Faces[nFace] := dat;


    Inc(nFace);
    result:=True;
  end;

  //座標が同じで、色も同じ、という点をまとめる
  procedure CompactPoints;
  var
    i,j:Integer;
    tbl:Array of Integer; //頂点番号変換テーブル
    compacted:Boolean;       //着目してた頂点は、まとめの対象になったか
    nCompacted:Integer;      //今までまとめられた点の数
    p:pInteger;
  begin
    SetLength(tbl, nPoint);


    //まず、コンパクション後の頂点が、コンパクション前のどの頂点にあたるのかを調べる
    //コンパクション後のi番の頂点は、コンパクション前のtbl[i]番の頂点に相当する
    nCompacted:=0;
    tbl[0]:=0;
    For i:=1 to nPoint-1 do begin

      compacted:=False;
      for j:=0 to i-1 do begin
        //以前に探索した点と同じ座標、同じ色なら、まとめる
        if (Points[i].Pos.x = Points[j].Pos.x) And (Points[i].Pos.y = Points[j].Pos.y) And (Points[i].Pos.z = Points[j].Pos.z) And (Points[i].Color = Points[j].Color) then begin
          compacted:=True;
          tbl[i]:=tbl[j];
          Inc(nCompacted);
          break;
        end;
      end;

      if not compacted then
        tbl[i]:=i - nCompacted;
      
    end;

    //コンパクション
    for i:=0 to nPoint-1 do begin
      Points[tbl[i]]:=Points[i];
    end;
    nPoint:=tbl[nPoint-1]+1;
    SetLength(Points, nPoint);


    //線データのポイント情報を書き換え
    For i:=0 to nLine-1 do begin
      with Lines[i] do begin
        startPoint:=tbl[startPoint];
        endPoint:=tbl[endPoint];
      end;
    end;


    //面データのポイント情報を書き換え
    For i:=0 to nFace-1 do begin
      p:=Faces[i].Points;
      For j:=0 to Faces[i].LineCount-1 do begin
        p^:=tbl[p^];
        Inc(p);
      end;
    end;


    tbl:=Nil;
  end;


begin
  line:=0;
  nPoint:=0;
  nLine:=0;
  nFace:=0;

  //格納すべきメッシュリストをクリア
  meshes.Clear;


  //テキストファイルとして開く
  src:=TStringList.Create;
  src.LoadFromStream(source);

  { ヘッダの解析 }
  if GetStr <> 'ROKU4' then begin
    DGCarad_PutDebugMessage('ROK読み込みのエラーです');
    exit;
  end;

  //ポイントヘッダまで飛ばす
  while GetStr <> 'POINT' do
    ;

  {データ読み読み}
  SetLength(Points, 1024);
  SetLength(Lines, 1024);
  SetLength(Faces, 1024);

  repeat ; until not GetPoint;  //点
  repeat ; until not GetLine;   //線
  repeat ; until not GetFace;   //面
  Inc(nGroup);  //GetLineで最大グループ番号が求まっているので、1足してグループ数に

  repeat ; until GetStr = 'PALC0'; //パレット
  nColor:=GetInt; //色数
  SetLength(Colors, nColor);
  GetInt;   //ダミー
  for i:=0 to nColor-1 do begin
    with Colors[i]  do begin
      r0:=GetInt;
      g0:=GetInt;
      b0:=GetInt;
      r1:=GetInt;
      g1:=GetInt;
      b1:=GetInt;
      GetInt; //ダミー
      col:=D3DCOLOR_XRGB(r0,g0,b0);
    end;
  end;


  { 面法線の計算 }
  For i:=0 to nFace-1 do begin

    pP:=Faces[i].Points;
    For j:=0 to 2 do begin
      vn[j]:=Points[pP^].Pos;
      Inc(pP);
    end;

    Faces[i].Normal:=NowNormalFromPoints(vn[0],vn[1],vn[2]);
  end;

  {面の重心の計算}
  For i:=0 to nFace-1 do begin
    vg:=Vector(0,0,0);
    pP:=Faces[i].Points;
    For j:=0 to Faces[i].LineCount-1 do begin
      vg:=NowAdd(vg, Points[pP^].Pos);
      Inc(pP);
    end;
    Faces[i].GP:=NowScale(vg, 1.0 / Faces[i].LineCount);
  end;

  //頂点のコンパクション・・・同じ座標、同じ色の頂点は同一とみなす
  //これはメッシュを起こしてからやるので、要らない
  //CompactPoints;


  { 面の二重化 }
  SetLength(Faces, nFace*2);
  For i:=nFace to nFace*2-1 do begin
    //コピー
    Faces[i]:=Faces[i-nFace];

    //点の回り方を逆にする
    GetMem(Faces[i].Points, Faces[i].LineCount * Sizeof(Integer));
    pPd:=Faces[i].Points;
    pPs:=Faces[i-nFace].Points;
    Inc(pPs, Faces[i].LineCount-1);

    For j:=0 to Faces[i].LineCount-1 do begin
      pPd^:=pPs^;
      Inc(pPd);
      Dec(pPs);
    end;

    //線情報もコピー
    GetMem(Faces[i].Lines, Faces[i].LineCount * Sizeof(Integer));
    pPd:=Faces[i].Lines;
    pPs:=Faces[i-nFace].Lines;
    Inc(pPs, Faces[i].LineCount-1);

    For j:=0 to Faces[i].LineCount-1 do begin
      pPd^:=pPs^;
      Inc(pPd);
      Dec(pPs);
    end;

    //法線も反転する
    Faces[i].Normal:=NowNeg(Faces[i].Normal);
  end;
  nFace:=nFace*2;



  { 各頂点がどの面に含まれているかを調べる }
  SetLength(homeFaces, nPoint);
  For i:=0 to nPoint-1 do begin
    SetLength(homeFaces[i], 16);
    homeFaces[i, 0]:=0;         //homeFaces[i,0]に頂点iを含む面の枚数を入れ、[i,1]以降は頂点iを含む面の番号
  end;

  For i:=0 to (nFace div 2) - 1 do begin
    pP:=Faces[i].Points;

    For j:=0 to Faces[i].LineCount-1 do begin

      //容量が足りなかったら、２倍に拡張
      Inc(homeFaces[pP^,0], 2); //これから2つ追加するよー
      if Length(homeFaces[pP^]) <= homeFaces[pP^,0] then
        SetLength(homeFaces[pP^], Length(homeFaces[pP^])*2);

      //追加
      homeFaces[pP^, homeFaces[pP^,0]-1]:=i;
      homeFaces[pP^, homeFaces[pP^,0]  ]:=i + nFace div 2; //面を２重化してあるので、同じ点を含む面は２枚単位で存在する

      Inc(pP);
    end;


  end;



  { TSXMeshの生成 }

  SetLength(nVertices, nGroup);
  SetLength(nIndices, nGroup);

  //まず、頂点数の計算
  //　六角の点とSXの頂点は一対一で対応しない。六角の点は色の違う面の境界になる事がある。
  //　よって、各面を構成する点を一つずつ調べて頂点を生成
  For i:=0 to nFace-1 do begin
    Inc(nVertices[Faces[i].iGroup], Faces[i].LineCount);
    Inc(nIndices[Faces[i].iGroup], (Faces[i].LineCount-2)*3);
  end;

  //生成
  For g:=0 to nGroup-1 do begin
    if (nVertices[g] <> 0) And (nIndices[g] <> 0) then begin
      meshes.Add(TSXMesh.Create(DG));
      //meshes[g].SetupBuffers(nVertices[g], nIndices[g], FVF_SXVERTEX, Sizeof(TSXVertex), D3DPOOL_MANAGED);
      meshes[g].SetupBuffers(nVertices[g], nIndices[g], FVF_ROK, Sizeof(TROKVertex), D3DPOOL_MANAGED);
    end else begin
      //頂点の無いグループは、Nilを入れる
      meshes.Add(Nil);
    end;
  end;

  nVertices:=Nil;
  nIndices:=Nil;



  {頂点情報のセット}
  SetLength(pV, nGroup);
  For g:=0 to nGroup-1 do begin
    if meshes[g]<>Nil then
      pV[g]:=meshes[g].VBuf.Lock(0,0,0);
  end;

  epsilon:=Cos(SmoothDegree / 180 * Pi); //内積がepsilon以上なら纏める

  For i:=0 to nFace -1 do begin
    //gに着目面のグループ番号を入れる
    g:=Faces[i].iGroup;

    //頂点作成
    pP:=Faces[i].Points;
    For j:=0 to Faces[i].LineCount-1 do begin
      normal:=Faces[i].Normal;

      //pV^:=SXVertex(p.Pos.x, p.Pos.y, p.Pos.z, Faces[i].Normal.x,Faces[i].Normal.y,Faces[i].Normal.z, Colors[Faces[i].Color].col, $00000000, 0.0,0.0);
      //着目している頂点を含む面について、対抗角度が小さければ法線を纏める
      normal:=Faces[i].Normal;

      For k:=1 to homeFaces[pP^,0] do begin
        //注目してる面の法線は、既に含まれているのでスキップ
        if homeFaces[pP^, k] = i then
          continue;

        //別のグループの面だったら、法線の計算の際に考慮しない
        if Faces[homeFaces[pP^, k]].iGroup <> g then
          continue;

        //法線同士のCos取ってepsilon以上ならまとめる
        phi:=NowDotProduct(Faces[i].Normal, Faces[homeFaces[pP^,k]].Normal);

        if (phi > epsilon) then
          normal:=NowAdd(normal, Faces[homeFaces[pP^,k]].Normal);

      end;


      //代入
      p:=Points[pP^];
      normal:=NowNormalize(normal);

      //normal:=Faces[i].Normal;
      //pV[g]^:=ROKVertex(p.Pos.x, p.Pos.y, p.Pos.z, normal.x,normal.y,normal.z, Colors[Faces[i].Color].col, $FFFFFFFF, 0.0,0.0);
      pV[g]^:=ROKVertex(p.Pos,normal,Colors[Faces[i].Color].col, 0,0);

      Inc(pV[g]);
      Inc(pP);
    end;

  end;

  For g:=0 to nGroup-1 do begin
    if meshes[g]<>Nil then
      meshes[g].VBuf.Unlock;
  end;

  pV:=Nil;


  //インデクスのセット…凸多角形→3角形変換はここでまかなう
  SetLength(iI,nGroup);
  SetLength(iV,nGroup);

  For g:=0 to nGroup-1 do begin
    if meshes[g]<>Nil then
      meshes[g].IBuf.Lock(0,0,0);
  end;

  For i:=0 to nFace-1 do begin
    g:=Faces[i].iGroup;

    For j:=0 to Faces[i].LineCount-3 do begin
      meshes[g].IBuf.Indices[iI[g]  ]:=iV[g];
      meshes[g].IBuf.Indices[iI[g]+1]:=(iV[g] + 1 + j);
      meshes[g].IBuf.Indices[iI[g]+2]:=(iV[g] + 2 + j);
      Inc(iI[g],3);
    end;
    Inc(iV[g],Faces[i].LineCount);

  end;

  For g:=0 to nGroup-1 do begin
    if meshes[g]<>Nil then
      meshes[g].IBuf.Unlock;
  end;

  iV:=Nil;
  iI:=Nil;


  //コンパクションと、バウンディング情報の更新
  For g:=0 to nGroup-1 do begin
    if meshes[g]<>Nil then begin
      //meshes[g].Compact;
      //meshes[g].Optimize;
      meshes[g].UpdateBounds;
    end;
  end;


  { 法線生成
  pV:=mesh.VBuf.Lock(0,0,0);
  mesh.IBuf.Lock(0,0,0);

  //まず、全部の面の法線を計算しておく
  SetLength(Normals, mesh.FaceCount);
  For i:=0 to mesh.FaceCount-1 do begin
    For j:=0 to 2 do
      mesh.VBuf.GetVertex(mesh.IBuf.Indices[i*3+j], vn[j]);

    Normals[i]:=NowNormalFromPoints(vn[0],vn[1],vn[2]);
    //DGCarad_PutDebugMessage(Format('NormalSrc #%.5d : %f,%f,%f',[i, normals[i].x, normals[i].y, normals[i].z]));
  end;



  //各頂点は、どの面に属するのか調べる
  SetLength(homeFaces, mesh.VBuf.Count);  //各頂点がどの面に属するかのバッファを作成
  For i:=0 to mesh.VBuf.Count-1 do begin
    SetLength(homeFaces[i], 8);           //第一要素には、属してる面の個数を代入
    homeFaces[i,0]:=0;
  end;

  For i:=0 to mesh.FaceCount-1 do begin
    For j:=0 to 2 do begin
      iV:=mesh.IBuf.Indices[i*3+j];

      //容量が足りなかったら、２倍に拡張
      Inc(homeFaces[iV,0]);
      if Length(homeFaces[iV]) <= homeFaces[iV,0] then
        SetLength(homeFaces[iV], Length(homeFaces[iV])*2);

      //追加
      homeFaces[iV, homeFaces[iV,0]]:=i;
    end;
  end;


  //面同士の法線の角度が近い面に存在する座標の等しい頂点は法線を同じにする


  //法線を計算する
  For i:=0 to mesh.VBuf.Count-1 do begin

    normal:=Vector(0,0,0);
    For j:=1 to homeFaces[i,0] do begin
      normal:=NowAdd(normal,Normals[homeFaces[i,j]]);
    end;

    //法線を正規化して代入
    pV^.normal:=NowNormalize(normal);
    Inc(pV);


    //DGCarad_PutDebugMessage(Format('Normal #%.5d : %f,%f,%f',[i, normal.x, normal.y, normal.z]));
  end;
  mesh.VBuf.Unlock;
  mesh.IBuf.Unlock;
  }


  //おつかれ〜
  src.Free;

  For i:=0 to High(homeFaces) do
    homeFaces[i]:=Nil;
  homeFaces:=Nil;

  Colors:=Nil;
  Points:=Nil;
  Lines:=Nil;


  For i:=0 to nFace-1 do begin
    FreeMem(Faces[i].Lines);
    FreeMem(Faces[i].Points);
  end;

  Faces:=Nil;

end;


procedure ROKLoadFromFile(DG:TDGCarad; filename:String; meshes:TSXMeshList; smoothDegree:Single; qdaName:String = '');
var
  src:TStream;
begin
  if qdaName = '' then begin
    src:=TFileStream.Create(filename, fmOpenRead);
  end else begin
    src:=ExtractFromQDAFile(qdaName, fileName) ;
  end;

  ROKLoadFromStream(DG, src, meshes, smoothDegree);
  src.Free;
end;


end.
