//
// This unit is part of the GLScene Engine, http://glscene.org
//

unit GLRagdoll;

(* Base abstract ragdoll class. Should be extended to use any physics system. *)

interface

{$I GLScene.inc}

uses
  GLScene,
  GLPersistentClasses, 
  GLVectorGeometry, 
  GLVectorFileObjects,
  GLVectorLists,
  GLObjects;

type
  TGLRagdoll = class;
  TGLRagdolBone = class;

  TGLRagdolJoint = class
  end;

  TGLRagdolBoneList = class (TPersistentObjectList)
  private
     FRagdoll : TGLRagdoll;
  protected
    function GetRagdollBone(Index: Integer) : TGLRagdolBone;
  public
    constructor Create(Ragdoll: TGLRagdoll); reintroduce;
    destructor Destroy; override;
    procedure WriteToFiler(writer : TVirtualWriter); override;
    procedure ReadFromFiler(reader : TVirtualReader); override;
    property Ragdoll : TGLRagdoll read FRagdoll;
    property Items[Index: Integer] : TGLRagdolBone read GetRagdollBone; default;
	end;
	TGLRagdolBone = class (TGLRagdolBoneList)
  private
    FOwner : TGLRagdolBoneList;
    FName : String;
    FBoneID : Integer; //Refering to TGLActor Bone
    FBoundMax: TAffineVector;
    FBoundMin: TAffineVector;
    FBoundBoneDelta: TAffineVector; //Stores the diference from the bone.GlobalMatrix to the center of the bone's bounding box
    FOrigin: TAffineVector;
    FSize: TAffineVector;
    FBoneMatrix: TMatrix;
    FJoint: TGLRagdolJoint;
    FOriginalMatrix: TMatrix; //Stores the Bone.GlobalMatrix before the ragdoll start
    FReferenceMatrix: TMatrix; //Stores the first bone matrix to be used as reference
    FAnchor: TAffineVector; //The position of the joint
    procedure CreateBoundingBox;
    procedure SetAnchor(const Anchor: TAffineVector);
    procedure AlignToSkeleton;
    procedure CreateBoundsChild;
    procedure StartChild;
    procedure AlignChild;
    procedure UpdateChild;
    procedure StopChild;
  protected
    function GetRagdollBone(Index: Integer) : TGLRagdolBone;
    procedure Start; virtual; abstract;
    procedure Align; virtual; abstract;
    procedure Update; virtual; abstract;
    procedure Stop; virtual; abstract;
  public
    constructor CreateOwned(aOwner : TGLRagdolBoneList);
    constructor Create(Ragdoll: TGLRagdoll);
    destructor Destroy; override;
    procedure WriteToFiler(writer : TVirtualWriter); override;
    procedure ReadFromFiler(reader : TVirtualReader); override;
    property Owner : TGLRagdolBoneList read FOwner;
    property Name : String read FName write FName;
    property BoneID : Integer read FBoneID write FBoneID;
    property Origin : TAffineVector read FOrigin;
    property Size : TAffineVector read FSize;
    property BoneMatrix : TMatrix read FBoneMatrix;
    property ReferenceMatrix : TMatrix read FReferenceMatrix;
    property Anchor : TAffineVector read FAnchor;
    property Joint : TGLRagdolJoint read FJoint write FJoint;
    property Items[Index: Integer] : TGLRagdolBone read GetRagdollBone; default;
	end;

  TGLRagdoll = class(TPersistentObject)
	private
    FOwner : TGLBaseMesh;
    FRootBone : TGLRagdolBone;
    FEnabled: Boolean;
    FBuilt: Boolean;
  public
    constructor Create(AOwner : TGLBaseMesh); reintroduce;
    destructor Destroy; override;
    procedure WriteToFiler(writer : TVirtualWriter); override;
    procedure ReadFromFiler(reader : TVirtualReader); override;
    {Must be set before build the ragdoll }
    procedure SetRootBone(RootBone: TGLRagdolBone);
    {Create the bounding box and setup the ragdoll do be started later }
    procedure BuildRagdoll;
    procedure Start;
    procedure Update;
    procedure Stop;
    property Owner : TGLBaseMesh read FOwner;
    property RootBone : TGLRagdolBone read FRootBone;
    property Enabled : Boolean read FEnabled;
 end;

//------------------------------------------------------------------------	
implementation
//------------------------------------------------------------------------	

uses
  GLVectorTypes;

{ TGLRagdolBoneList }

constructor TGLRagdolBoneList.Create(Ragdoll: TGLRagdoll);
begin
  inherited Create;
  FRagdoll := Ragdoll;
end;

destructor TGLRagdolBoneList.Destroy;
var i: integer;
begin
  for i:=0 to Count-1 do Items[i].Destroy;
  inherited;
end;

function TGLRagdolBoneList.GetRagdollBone(Index: Integer): TGLRagdolBone;
begin
  Result:=TGLRagdolBone(List^[Index]);
end;

procedure TGLRagdolBoneList.ReadFromFiler(reader: TVirtualReader);
begin
  inherited;
  //Not implemented
end;

procedure TGLRagdolBoneList.WriteToFiler(writer: TVirtualWriter);
begin
  inherited;
  //Not implemented
end;

{ TGLRagdolBone }

constructor TGLRagdolBone.Create(Ragdoll: TGLRagdoll);
begin
  inherited Create(Ragdoll);
end;

procedure TGLRagdolBone.CreateBoundingBox;
var
  bone: TGLSkeletonBone;
  i, j: integer;
  BoneVertices : TAffineVectorList;
  BoneVertex, max,min: TAffineVector;
  invMat, mat: TMatrix;
begin
  bone := Ragdoll.Owner.Skeleton.BoneByID(FBoneID);

  //Get all vertices weighted to this bone
  BoneVertices:=TAffineVectorList.Create;
  for i:=0 to Ragdoll.Owner.MeshObjects.Count-1 do
  with TGLSkeletonMeshObject(Ragdoll.Owner.MeshObjects[i]) do
    for j:=0 to Vertices.Count-1 do
      if bone.BoneID = VerticesBonesWeights[j][0].BoneID then
        BoneVertices.FindOrAdd(Vertices[j]);

  invMat := bone.GlobalMatrix;
  InvertMatrix(invMat);

  //For each vertex, get the max and min XYZ (Bounding box)
  if BoneVertices.Count > 0 then
  begin
    BoneVertex := VectorTransform(BoneVertices[0], invMat);
    max := BoneVertex;
    min := BoneVertex;
    for i:=1 to BoneVertices.Count-1 do begin
      BoneVertex := VectorTransform(BoneVertices[i], invMat);
      if (BoneVertex.X > max.X) then max.X := BoneVertex.X;
      if (BoneVertex.Y > max.Y) then max.Y := BoneVertex.Y;
      if (BoneVertex.Z > max.Z) then max.Z := BoneVertex.Z;

      if (BoneVertex.X < min.X) then min.X := BoneVertex.X;
      if (BoneVertex.Y < min.Y) then min.Y := BoneVertex.Y;
      if (BoneVertex.Z < min.Z) then min.Z := BoneVertex.Z;
    end;

    FBoundMax := max;
    FBoundMin := min;
    //Get the origin and subtract from the bone matrix
    FBoundBoneDelta := VectorScale(VectorAdd(FBoundMax, FBoundMin), 0.5);
  end else begin
    FBoundMax := NullVector;
    FBoundMin := NullVector;
  end;

  AlignToSkeleton;
  FReferenceMatrix := FBoneMatrix;
  mat := MatrixMultiply(bone.GlobalMatrix,FRagdoll.Owner.AbsoluteMatrix);
  //Set Joint position
  SetAnchor(AffineVectorMake(mat.V[3]));

  BoneVertices.Free; // NEW1
end;

constructor TGLRagdolBone.CreateOwned(aOwner: TGLRagdolBoneList);
begin
	Create(aOwner.Ragdoll);
  FOwner:=aOwner;
  aOwner.Add(Self);
end;

destructor TGLRagdolBone.Destroy;
begin
  inherited;
end;

procedure TGLRagdolBone.AlignToSkeleton;
var
  o: TAffineVector;
  bone: TGLSkeletonBone;
  mat, posMat: TMatrix;
  noBounds: Boolean;
begin
  bone := Ragdoll.Owner.Skeleton.BoneByID(FBoneID);
  noBounds := VectorIsNull(FBoundMax) and VectorIsNull(FBoundMin);
  //Get the bone matrix relative to the Actor matrix
  mat := MatrixMultiply(bone.GlobalMatrix,FRagdoll.Owner.AbsoluteMatrix);
  //Set Rotation
  FBoneMatrix := mat;
  NormalizeMatrix(FBoneMatrix);

  if (noBounds) then
  begin
    FOrigin := AffineVectorMake(mat.V[3]);
    FSize := AffineVectorMake(0.1, 0.1, 0.1);
  end else begin
    //Set Origin
    posMat := mat;
    posMat.V[3] := NullHmgVector;
    o := VectorTransform(FBoundBoneDelta, posMat);
    FOrigin := VectorAdd(AffineVectorMake(mat.V[3]), o);
    //Set Size
    FSize := VectorScale(VectorSubtract(FBoundMax, FBoundMin),0.9);
    FSize.X := FSize.X*VectorLength(mat.V[0]);
    FSize.Y := FSize.Y*VectorLength(mat.V[1]);
    FSize.Z := FSize.Z*VectorLength(mat.V[2]);
  end;
  //Put the origin in the BoneMatrix
  FBoneMatrix.V[3] := VectorMake(FOrigin,1);
end;

function TGLRagdolBone.GetRagdollBone(Index: Integer): TGLRagdolBone;
begin
  Result:=TGLRagdolBone(List^[Index]);
end;

procedure TGLRagdolBone.ReadFromFiler(reader: TVirtualReader);
begin
  inherited;

end;

procedure TGLRagdolBone.StartChild;
var i: integer;
begin
  FOriginalMatrix := Ragdoll.Owner.Skeleton.BoneByID(FBoneID).GlobalMatrix;
  AlignToSkeleton;
  Start;
  for i := 0 to Count-1 do items[i].StartChild;
end;

procedure TGLRagdolBone.UpdateChild;
var i: integer;
begin
  Update;
  for i := 0 to Count-1 do items[i].UpdateChild;
end;

procedure TGLRagdolBone.WriteToFiler(writer: TVirtualWriter);
begin
  inherited;

end;

procedure TGLRagdolBone.StopChild;
var i: integer;
begin
  Stop;
  Ragdoll.Owner.Skeleton.BoneByID(FBoneID).SetGlobalMatrix(FOriginalMatrix);
  for i := 0 to Count-1 do items[i].StopChild;
end;

procedure TGLRagdolBone.CreateBoundsChild;
var i: integer;
begin
  CreateBoundingBox;
  for i := 0 to Count-1 do items[i].CreateBoundsChild;
end;

procedure TGLRagdolBone.SetAnchor(const Anchor: TAffineVector);
begin
  FAnchor := Anchor;
end;

procedure TGLRagdolBone.AlignChild;
var i: integer;
begin
  Align;
  Update;
  for i := 0 to Count-1 do items[i].AlignChild;
end;

{ TGLRagdoll }

constructor TGLRagdoll.Create(AOwner : TGLBaseMesh);
begin
  FOwner := AOwner;
  FEnabled := False;
  FBuilt := False;
end;

destructor TGLRagdoll.Destroy;
begin
  if FEnabled then Stop;
  inherited Destroy;
end;

procedure TGLRagdoll.ReadFromFiler(reader: TVirtualReader);
begin
  inherited;
end;
procedure TGLRagdoll.SetRootBone(RootBone: TGLRagdolBone);
begin
  FRootBone := RootBone;
end;

procedure TGLRagdoll.Start;
begin
  Assert(FBuilt, 'First you need to build the ragdoll. BuildRagdoll;');
  if (FEnabled) then Exit;
  FEnabled:= True;
  //First start the ragdoll in the reference position
  RootBone.StartChild;
  //Now align it to the animation
  RootBone.AlignChild;
  //Now it recalculate the vertices to use as reference
  FOwner.Skeleton.StartRagDoll;
end;

procedure TGLRagdoll.Update;
begin
  if FEnabled then
  begin
    RootBone.UpdateChild;
    FOwner.Skeleton.MorphMesh(true);
  end;
end;

procedure TGLRagdoll.Stop;
begin
  if not FEnabled then Exit;
  FEnabled := False;
  RootBone.StopChild;
  //Restore the old information
  FOwner.Skeleton.StopRagDoll;
  FOwner.Skeleton.MorphMesh(true);
end;

procedure TGLRagdoll.WriteToFiler(writer: TVirtualWriter);
begin
  inherited;

end;

procedure TGLRagdoll.BuildRagdoll;
begin
  Assert(RootBone <> nil, 'First you need to set the root bone. SetRootBone();');
  RootBone.CreateBoundsChild;
  FBuilt := True;
end;

end.
