unit Primitives;

//Direct3D IMを使ったプリミティブ描画エンジン

interface

uses
  SysUtils,Windows,
  d3d,d3dtypes,d3dcaps,
  dddd,s_MathPack,dddd3dut;


type

TBlendMode = (bmDecal,bmAdd,bmAlpha,bmMul);


TPrimitive = packed record
  Texture:TDDDDTexture;       //テクスチャ
  Bound: Array[0..3] of D3DTLVertex; //四角形
  ZOrder:Integer;             //Zオーダ
  BlendMode:TBlendMode;       //ブレンドモード
end;
PPrimitive = ^TPrimitive;


TTextureCoord = packed record
  Texture:TDDDDTexture;
  Top,Left,Bottom,Right:Single;       //テクスチャ座標
end;
PTextureCoord = ^TTextureCoord;

TSingleRect = packed record
  Left,Top,Right,Bottom:Single;
end;



TPrimitiveRenderer = class
  private
    QD:TDDDD;
    FZMax:Integer;      //Zオーダの最大値
    FMaxPrimitives:Integer;   //格納できるTLVの最大値

    ZSortBuf:^Integer;  //Zソート用バッファ
    ZSortCount:^Integer;//Zソート用カウンタ
    PrimBuf:PPrimitive; //プリミティブ入れ
    PrimCount:Integer;  //現在PrimBufに入ってるプリミティブの数
    Patterns:PTextureCoord; //テクスチャパターン
    FNPrims:Integer;    //最後にUpdateメソッドを発行した際の、プリミティブの数
    FColorKey:Boolean;  //カラーキーによる抜き色を行う？
    procedure setZMax(v:Integer);
  public
    constructor Create(DDCompo:TDDDD; PrimitiveCount:Integer; PatternCount:Integer);
    destructor Destroy; override;
    property ZMax:Integer read FZMax write setZMax;

    procedure SetPattern(idx:Integer; Tex:TDDDDTexture; Coord:TRect);  //パターンとしてTexのCoordで示される範囲を登録

    procedure Push(Tex:TDDDDTexture; p1,p2,p3,p4:D3DTLVertex; Z:Integer; blend:TBlendMode);    //TLVertexで四角形を入れる
    procedure Push2D(Tex:TDDDDTexture; p1,p2,p3,p4:TVector2D; Z:Integer; blend:TBlendMode; col:DWord);  //2D的な四角形

    procedure PushPattern(iPat:Integer; p1,p2,p3,p4:THgVector; Z:Integer; blend:TBlendMode; col:DWord);    //パターンをプッシュ(3次元)
    procedure PushPattern2D(iPat:Integer; p1,p2,p3,p4:TVector2D; Z:Integer; blend:TBlendMode; col:DWord);  //パターンをプッシュする
    procedure PushPatternRect(iPat:Integer; rect:TSingleRect; Z:Integer; blend:TBlendMode; col:DWord);                      //パターンをプッシュする(矩形)

    procedure BeginRender;               //Z値にしたがってソート
    procedure RenderOneLayer(Z:Integer); //単一のZ値を持つ集合だけ描画
    procedure EndRender;                 //スタックを空に
    procedure Render;                    //全部描画(BeginRender〜RenderOneLayer〜EndRender)

    property  nPrims:Integer read FNPrims;
    property  ColorKey:Boolean read FColorKey write FColorKey; 
end;



function SingleRect(x1,y1,x2,y2:Single):TSingleRect;



implementation

type
  TIntArray = array[0..$0FFFFFFF] of Integer;
  PIntArray = ^TIntArray;
  TPrimArray = array[0..$FFFFF] of TPrimitive;
  PPrimArray = ^TPrimArray;
  TTextureCoordArray = array[0..$FFFFF] of TTextureCoord;
  PTextureCoordArray = ^TTextureCoordArray;


function SingleRect(x1,y1,x2,y2:Single):TSingleRect;
begin
  with result do begin
    Left:=x1;
    Top:=y1;
    Right:=x2;
    Bottom:=y2;
  end;
end;


constructor TPrimitiveRenderer.Create(DDCompo:TDDDD; PrimitiveCount:Integer; PatternCount:Integer);
begin
  QD:=DDCompo;
  FMaxPrimitives:=PrimitiveCount;
  ZSortBuf:=Nil;
  ZSortCount:=Nil;
  PrimCount:=0;

  GetMem(PrimBuf,Sizeof(TPrimitive) * PrimitiveCount);
  GetMem(Patterns,Sizeof(TTextureCoord) * PatternCount);

  ZMax:=0;
  ColorKey:=True;
end;


destructor TPrimitiveRenderer.Destroy;
begin
  FreeMem(ZSortBuf);
  FreeMem(ZSortCount);
  FreeMem(Patterns);
  FreeMem(PrimBuf);
  
  inherited;
end;

procedure TPrimitiveRenderer.setZMax(v:Integer);
begin
  FZmax:=v;

  if ZSortBuf <> Nil then
    FreeMem(ZSortBuf);
  if ZSortCount <> Nil then
    FreeMem(ZSortCount);

  GetMem(ZSortBuf,(FZMax+1) * FMaxPrimitives * sizeof(Integer));
  GetMem(ZSortCount,(FZMax+1) * sizeof(Integer));
end;


//パターンとしてTexのCoordで示される範囲を登録
procedure TPrimitiveRenderer.SetPattern(idx:Integer; Tex:TDDDDTexture; Coord:TRect);
var
  pPat:PTextureCoord;
begin
  pPat:=@(PTextureCoordArray(Patterns)^[idx]);

  with pPat^ do begin
    Texture:=Tex;
    Top:=Coord.Top / Tex.Height;
    Left:=Coord.Left / Tex.Width;
    Bottom:=Coord.Bottom / Tex.Height;
    Right:=Coord.Right / Tex.Width;
  end;

end;


procedure TPrimitiveRenderer.Push(Tex:TDDDDTexture; p1,p2,p3,p4:D3DTLVertex; Z:Integer; blend:TBlendMode);    //TLVertexで四角形を入れる
begin
  with PPrimArray(PrimBuf)^[PrimCount] do begin
    Texture:=Tex;

    Bound[0]:=p1;
    Bound[1]:=p2;
    Bound[2]:=p3;
    Bound[3]:=p4;

    ZOrder:=Z;
    BlendMode:=blend;
  end;

  Inc(PrimCount);
end;

procedure TPrimitiveRenderer.Push2D(Tex:TDDDDTexture; p1,p2,p3,p4:TVector2D; Z:Integer; blend:TBlendMode; col:DWord);  //2D的な四角形
begin
  with PPrimArray(PrimBuf)^[PrimCount] do begin
    Texture:=Tex;

    Bound[0]:=makeTLV(p1.x,p1.y,2.0,0.5, col,0, 0,0);
    Bound[1]:=makeTLV(p2.x,p2.y,2.0,0.5, col,0, 1,0);
    Bound[2]:=makeTLV(p3.x,p3.y,2.0,0.5, col,0, 0,1);
    Bound[3]:=makeTLV(p4.x,p4.y,2.0,0.5, col,0, 1,1);

    ZOrder:=Z;
    BlendMode:=blend;
  end;

  Inc(PrimCount);
end;


//パターンをプッシュ(3次元)
procedure TPrimitiveRenderer.PushPattern(iPat:Integer; p1,p2,p3,p4:THgVector; Z:Integer; blend:TBlendMode; col:DWord);
var
  pPat:PTextureCoord;
begin
  with PPrimArray(PrimBuf)^[PrimCount] do begin

    pPat:=@(PTextureCoordArray(Patterns)^[iPat]);
    Texture:=pPat^.Texture;

    Bound[0]:=makeTLV(p1.x,p1.y,p1.z,p1.w, col,0, pPat^.Left,pPat^.Top);
    Bound[1]:=makeTLV(p2.x,p2.y,p2.z,p2.w, col,0, pPat^.Right,pPat^.Top);
    Bound[2]:=makeTLV(p3.x,p3.y,p3.z,p3.w, col,0, pPat^.Left,pPat^.Bottom);
    Bound[3]:=makeTLV(p4.x,p4.y,p4.z,p4.w, col,0, pPat^.Right,pPat^.Bottom);

    ZOrder:=Z;
    BlendMode:=blend;
  end;

  Inc(PrimCount);
end;

procedure TPrimitiveRenderer.PushPattern2D(iPat:Integer; p1,p2,p3,p4:TVector2D; Z:Integer; blend:TBlendMode; col:DWord);  //パターンをプッシュする
var
  pPat:PTextureCoord;
begin
  with PPrimArray(PrimBuf)^[PrimCount] do begin

    pPat:=@(PTextureCoordArray(Patterns)^[iPat]);
    Texture:=pPat^.Texture;

    Bound[0]:=makeTLV(p1.x,p1.y,2,0.5, col,0, pPat^.Left,pPat^.Top);
    Bound[1]:=makeTLV(p2.x,p2.y,2,0.5, col,0, pPat^.Right,pPat^.Top);
    Bound[2]:=makeTLV(p3.x,p3.y,2,0.5, col,0, pPat^.Left,pPat^.Bottom);
    Bound[3]:=makeTLV(p4.x,p4.y,2,0.5, col,0, pPat^.Right,pPat^.Bottom);

    ZOrder:=Z;
    BlendMode:=blend;
  end;

  Inc(PrimCount);
end;


//パターンをプッシュする(矩形)
procedure TPrimitiveRenderer.PushPatternRect(iPat:Integer; rect:TSingleRect; Z:Integer; blend:TBlendMode; col:DWord);
var
  pPat:PTextureCoord;
begin
  with PPrimArray(PrimBuf)^[PrimCount] do begin

    pPat:=@(PTextureCoordArray(Patterns)^[iPat]);
    Texture:=pPat^.Texture;

    Bound[0]:=makeTLV(rect.Left, rect.Top,    2,0.5, col,0, pPat^.Left,pPat^.Top);
    Bound[1]:=makeTLV(rect.Right,rect.Top,    2,0.5, col,0, pPat^.Right,pPat^.Top);
    Bound[2]:=makeTLV(rect.Left, rect.Bottom, 2,0.5, col,0, pPat^.Left,pPat^.Bottom);
    Bound[3]:=makeTLV(rect.Right,rect.Bottom, 2,0.5, col,0, pPat^.Right,pPat^.Bottom);

    ZOrder:=Z;
    BlendMode:=blend;
  end;

  Inc(PrimCount);
end;

//Z値にしたがってソート
procedure TPrimitiveRenderer.BeginRender;
var
  i:Integer;
  pCount:^Integer;    //各レイヤに、いくつ物体があるか
  pSort:^Integer;
  pPrim:PPrimitive;
begin
  FNPrims:=PrimCount;

  //FSortCountのクリア
  pCount:=Pointer(ZSortCount);
  ZeroMemory(pCount,Sizeof(Integer)*(FZMax+1));

  if PrimCount = 0 then
    Exit;

  //ビンソート
  pPrim:=@(PPrimArray(PrimBuf)^[PrimCount-1]);
  for i:=PrimCount-1 downto 0 do begin
    pSort:=Pointer(ZSortBuf);
    pCount:=Pointer(ZSortCount);
    if pPrim^.ZOrder > FZMax then begin
      DDDD_PutDebugMessage(Format('Primitives: Z=%dは無効です、Z値は0から%dの範囲にしてください',[pPrim^.ZOrder,FZMax]));
      continue;
    end;
    Inc(pCount,pPrim^.ZOrder);
    Inc(pSort,pPrim^.ZOrder * FMaxPrimitives + pCount^);
    pSort^:=i;  //FSortBuf[z][FSortCount[z]]:=i
    Inc(pCount^); //FSortCount[z]++

    Dec(pPrim);
  end;

end;

//単一のZ値を持つ集合だけ描画
procedure TPrimitiveRenderer.RenderOneLayer(Z:Integer);
var
  i:Integer;
  pSort:^Integer;
  pPrim:PPrimitive;

  prevTexture:TDDDDTexture;
  prevBlendMode:TBlendMode;
  nPrims:Integer;
begin

  if Z > FZMax then begin
    DDDD_PutDebugMessage(Format('Render: Z=%dは無効です、Z値は0から%dの範囲にしてください',[Z,FZMax]));
    exit;
  end;

  nPrims:=PIntArray(ZSortCount)^[Z];
  if nPrims = 0 then
    Exit;



  //レンダリングステートの設定
  with QD.D3DDevice do begin
    SetTexture(0,Nil);
    SetRenderState(D3DRENDERSTATE_ZENABLE,INteger(False));
    SetRenderState(D3DRENDERSTATE_ZWRITEENABLE,INteger(False));

    //SetTextureStageState(0, D3DTSS_MAGFILTER, Integer(D3DTFG_POINT));
    //SetTextureStageState(0, D3DTSS_MINFILTER, Integer(D3DTFG_POINT));

    SetTextureStageState(0, D3DTSS_MAGFILTER, Integer(D3DTFG_LINEAR));
    SetTextureStageState(0, D3DTSS_MINFILTER, Integer(D3DTFG_LINEAR));

    SetRenderState(D3DRENDERSTATE_STIPPLEDALPHA,Integer(False));
    SetRenderState(D3DRENDERSTATE_DITHERENABLE,Integer(False));
    SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE,Integer(False));
    SetRenderState(D3DRENDERSTATE_COLORKEYENABLE,Integer(ColorKey));
  end;
  prevTexture:=Nil;
  prevBlendMode:=bmDecal;

  pSort:=@(PIntArray(ZSortBuf)^[FMaxPrimitives*Z]);
  For i:=0 to nPrims-1 do begin
    pPrim:=@(PPrimArray(PrimBuf)^[pSort^]);

    //テクスチャの変更
    if pPrim^.Texture <> prevTexture then begin
      if pPrim^.Texture = Nil then
        QD.D3DDevice.SetTexture(0, Nil)
      else
        QD.D3DDevice.SetTexture(0, pPrim^.Texture.Surface);
      prevTexture:=pPrim^.Texture;
    end;

    //ブレンドモードの設定
    if pPrim^.BlendMode <> prevBlendMode then begin
      Case pPrim^.BlendMode of

        bmDecal:begin
          QD.D3DDevice.SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE,Integer(False));
        end;

        bmAdd: begin
          if prevBlendMode = bmDecal then begin
            QD.D3DDevice.SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE,Integer(True));

            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG1, Integer(D3DTA_TEXTURE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG2, Integer(D3DTA_DIFFUSE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLOROP, Integer(D3DTOP_MODULATE));

            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG1, Integer(D3DTA_TEXTURE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG2, Integer(D3DTA_DIFFUSE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAOP, Integer(D3DTOP_MODULATE));
          end;
          QD.D3DDevice.SetRenderState(D3DRENDERSTATE_SRCBLEND,Integer(D3DBLEND_ONE));      // ソースブレンド設定
          QD.D3DDevice.SetRenderState(D3DRENDERSTATE_DESTBLEND,Integer(D3DBLEND_ONE));     // ディスティネーション設定
        end;

        bmAlpha: begin
          if prevBlendMode = bmDecal then begin
            QD.D3DDevice.SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, Integer(True));     //αを有効に☆

            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG1, Integer(D3DTA_TEXTURE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG2, Integer(D3DTA_DIFFUSE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLOROP, Integer(D3DTOP_MODULATE));

            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG1, Integer(D3DTA_TEXTURE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG2, Integer(D3DTA_DIFFUSE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAOP, Integer(D3DTOP_MODULATE));
          end;
          QD.D3DDevice.SetRenderState(D3DRENDERSTATE_SRCBLEND,Integer(D3DBLEND_SRCALPHA));      // ソースブレンド設定
          QD.D3DDevice.SetRenderState(D3DRENDERSTATE_DESTBLEND,Integer(D3DBLEND_INVSRCALPHA));     // ディスティネーション設定
        end;

        bmMul: begin
          if prevBlendMode = bmDecal then begin
            QD.D3DDevice.SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, Integer(True));     //αを有効に☆

            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG1, Integer(D3DTA_TEXTURE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG2, Integer(D3DTA_DIFFUSE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_COLOROP, Integer(D3DTOP_MODULATE));

            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG1, Integer(D3DTA_TEXTURE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG2, Integer(D3DTA_DIFFUSE));
            QD.D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAOP, Integer(D3DTOP_MODULATE));
          end;
          QD.D3DDevice.SetRenderState(D3DRENDERSTATE_SRCBLEND,Integer(D3DBLEND_DESTCOLOR));      // ソースブレンド設定
          QD.D3DDevice.SetRenderState(D3DRENDERSTATE_DESTBLEND,Integer(D3DBLEND_ZERO));     // ディスティネーション設定
        end;

      end;
      prevBlendMode:=pPrim^.BlendMode;
    end;

    try
      QD.D3DDevice.DrawPrimitive(D3DPT_TRIANGLESTRIP,D3DFVF_TLVERTEX,@(pPrim^.Bound),4, 0);
      Inc(pSort);
    except
      DDDD_PutDebugMessage('FP Fault');
      Halt;
    end;
  end;


end;

//スタックを空に
procedure TPrimitiveRenderer.EndRender;
begin
  PrimCount:=0;
end;



//全部描画
procedure TPrimitiveRenderer.Render;
var
  i:Integer;
begin
  BeginRender;
  For i:=0 to FZMax do
    RenderOneLayer(i);
  EndRender;
end;


end.
