Create A Mesh (CustomMesh)

Developers need to create meshes and models themselves at runtime. Our 3D format loaders already use those ways to setup their model data. So it is possible for users to build their own import-format or mesh-generator, too.

The Fast Way

There are a lot of applications need to setup vertex data manually. The mesh definition provides a easy-to-use method: AddPolygon().

In the following example we declare our own class inherited by TGorillaMesh. In the constructor we create an empty TMeshDef instance. Afterwards we setup a Build() method where we produce polygons of our mesh.

In our example we only push vertex position data to our mesh. Normals, Tangents, Binormals and Texture-Coordinates will automatically be created by helper functions:

  • CalcTextureCoordinates
  • CalcFaceNormals
  • CalcTangentBinormals
uses
  Gorilla.Mesh, Gorilla.DefTypes;
 
TUserMesh = class(TGorillaMesh)
    public
      constructor Create(AOwner : TComponent); override;
 
      procedure Build();
  end;
 
  [...]
 
constructor TUserMesh.Create(AOwner: TComponent);
begin
  inherited;
 
  FDef := TMeshDef.Create(nil);
end;
 
procedure TUserMesh.Build();
begin
  with TMeshDef(FDef) do
  begin
    AddPolygon([PointF(-0.5, -0.5), PointF(0.5, -0.5), PointF(0.5, 0.5), PointF(-0.5, 0.5)]);
    AddPolygon([PointF(-0.5, -1.5), PointF(0.5, -1.5), PointF(0.5, -0.75), PointF(-0.5, -0.75)]);
    AddPolygon([PointF(-1.5, -1.5), PointF(-0.75, -1.5), PointF(-0.75, -0.75), PointF(-1.5, -0.75)]);
 
    // after all polygon vertices were added
    // compute tex-coords, normals, tangents and binormals
    CalcTextureCoordinates();
    CalcFaceNormals();
    CalcTangentBinormals();
 
    // after vertex data was finally setup - set mesh to static and push data
    // to GPU
    IsStatic := true;
  end;
end;
 
[...]
 
// Create a visual instance at runtime
FUserMesh := TUserMesh.Create(GorillaViewport1);
FUserMesh.Parent := GorillaViewport1;
FUserMesh.Position.Point := Point3D(3, -1, 3);
FUserMesh.RotationAngle.X := -45;
 
// Build the vertex data
FUserMesh.Build();
 
// Apply an existing material
FUserMesh.MaterialSource := GorillaLambertMaterialSource1;

The More Powerful Way

The way above describes an easy and fast way to setup mesh data. But often more complex approaches are needed.

So what about?

  • Vertex normals, tangents or texture-coordinates, …
  • Mesh Hierarchy
  • Materials
  • Armatures with Joints and Controllers
  • Animations

TVertexCache

For adding complex and mixed vertex data for vertex-positions, normals, tangents, binormals, texture-coordinates and colors, we provide the TVertexCache class. A number of properties and methods helps you to manage this kind of data.

The recommended procedure is:

  1. buffer all vertex data (position, normals, texture-coordinates, …)
  2. add triangles/polygons from those

Properties

Property Descr
PositionSourceBuffer for all vertex position coordinates
NormalSourceBuffer for all vertex normal vectors
BinormalSourceBuffer for all binormal vectors
TangentSourceBuffer for all tangent vectors
TextureSource[Index : Integer]Buffer for multiple texture coordinates
ColorSource[Index : Integer]Buffer for multiple color values
IndexMapSome formats need mapping between vertices by index value. Also it is getting filled during auto-triangulation.
VerticesList of combined vertex data into a single structure (TVertexData): TList<TVertexData>
IndicesList of arrays with 3 ordinal values representing triangles: TList<TTriangleIndex>

Append Vertex Data

Method Argument Descr
AddPositionSourceTSingleDynArrayAdd a single vertex position by an array of 3 floating point values representing x, y and z
AddPositionSourceTPoint3DAdd a single 3D vertex position
AddNormalSourceTSingleDynArrayAdd a single normal vector by an array of 3 floating point values representing x, y and z
AddNormalSourceTPoint3DAdd a single 3D normal vector
AddBinormalSourceTSingleDynArrayAdd a single binormal vector by an array of 3 floating point values representing x, y and z
AddBinormalSourceTPoint3DAdd a single 3D binormal vector
AddTangentSourceTSingleDynArrayAdd a single tangent vector by an array of 3 floating point values representing x, y and z
AddTangentSourceTArray<TVector3D>Add multiple tangent vectors by an array of tangent vectors
AddTangentSourceTPoint3DAdd a single 3D tangent vector
AddTextureSourceAIndex, TSingleDynArray, AConvertToUVAdd a single texture coordinate by an array of 2 floating point values representing x, y. AConvertToUV will convert between st and uv coordinates.
AddTextureSourceAIndex, TPointF, AConvertToUVAdd a single 2D texture coordinate. AConvertToUV will convert between st and uv coordinates.
AddColorSourceAIndex, TSingleDynArrayAdd single vertex color by an array of 4 floating point values representing r,g,b,a
AddColorSourceAIndex, TAlphaColorFAdd a single color value

Push Multiple Vertex Data

Pushing multiple vertex data will always clear the specific buffer and will not append the data.

MethodArgumentDescr
SetPositionSourceTSingleDynArrayAdds an array of multiple vertex positions flattened into a straight array of floating point values, like [X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3, …]
SetNormalSourceTSingleDynArrayAdds an array of multiple normal vectors flattened into a straight array of floating point values, like [X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3, …]
SetNormalSourceTSingleDynArray, ATransformAdds an array of multiple normal vectors flattened into a straight array of floating point values, like [X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3, …] and transforms the normal vectors by the supplied transformation matrix.
SetBiNormalSourceTSingleDynArrayAdds an array of multiple binormal vectors flattened into a straight array of floating point values, like [X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3, …]
SetTangentSourceTSingleDynArrayAdds an array of multiple tangent vectors flattened into a straight array of floating point values, like [X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3, …]
SetTextureSourceAIndex, TSingleDynArray, AConvertToUVAdds an array of multiple texture coordinates flattened into a straight array of floating point values, like [X1,Y1, X2,Y2, X3,Y3, …] at a certain position (starting with zero). AConvertToUV allows to convert those values between st and uv format.
SetColorSourceAIndex,TSingleDynArrayAdds an array of multiple color values flattened into a straight array of floating point values, like [R1,G1,B1,A1, R2,G2,B2,A2, R3,G3,B3,A3, …] at a certain position (starting with zero)

Generate Triangles / Polygons

MethodDescr
function AddTriangle(const AIndexOffset : Integer; const ATriangle : TTriangleID; const AVertices : TList<TVertexData>; const AIndices : TList<TTriangleIndex>; const AVertexOrder : TVertexOrder = TVertexOrder.CCW) : Integer;Adds triangle vertices and indices to AVertices and AIndices based on the index values inside of TTriangleID VertexData values are taken from Cache.PositionSource and so on…
function AddPolygon(const AIndexOffset : Integer; const APolygon : TPolygonID; const AVertices : TList<TVertexData>; const AIndices : TList<TTriangleIndex>; const AVertexOrder : TVertexOrder = TVertexOrder.CCW) : Integer;Adds polygon vertices and indices to AVertices and AIndices based on the index values inside of TPolygonID VertexData values are taken from Cache.PositionSource and so on… CAUTION: this method triangulates the polygon automatically!

Example

In this detailed example, let's create a mesh the same like in the “fast-way” example above.

But here we're going further by adding normals, colors, materials, armature+bones and a skin-animation.

In the first step we're going to show the usage of TVertexCache to setup mesh data.

Adding MeshData

uses
  System.Generics.Collections, System.Generics.Defaults,
  Gorilla.DefTypes, Gorilla.Loader;
 
type
  TUserModelBuilder = class
    protected
      class function DoBuildMesh(AOwner : TMeshDef) : TMeshDef;
 
    public
      class function Build() : TModelDef;
 
      class function AddMaterial(AModel : TModelDef; AMesh : TMeshDef;
        AId : String; AFileName : String) : TMaterialDef;
      class function AddArmatureAndController(AModel : TModelDef;
        AMesh : TMeshDef) : TArmatureDef;
      class function AddSkinAnimation(AModel : TModelDef;
        AArmature : TArmatureDef; AId : String) : TAnimationDef;
  end;
 
  [...]
 
class function TUserModelBuilder.Build() : TModelDef;
var LMesh : TMeshDef;
    LArma : TArmatureDef;
begin
  // Create the model container
  Result := TModelDef.Create(nil, nil);
  Result.Id := 'CustomModel1';
 
  // Create and add all meshes to it
  LMesh := DoBuildMesh(Result);
 
  // We generate texture coordinates, tangents and binormals automatically
  LMesh.CalcTextureCoordinates();
  LMesh.CalcTangentBinormals();
 
  // Add a material with texture
  AddMaterial(Result, LMesh, 'CustomMaterial1', 'brick-c.jpg');
 
  // Add an armature and skin controller
  LArma := AddArmatureAndController(Result, LMesh);
 
  // Add skin animation
  AddSkinAnimation(Result, LArma, 'Animation1');
end;
 
class function TUserModelBuilder.DoBuildMesh(AOwner : TMeshDef) : TMeshDef;
 
  function GetVertexID(const APos, ANormal, ATangent, AColor : Integer) : TVertexID;
  begin
    FillChar(Result, SizeOf(Result), 0);
 
    Result.Position := APos;
    Result.Normal := ANormal;
    Result.Tangent := ATangent;
    Result.Colors.Count := 1;
    Result.Colors.Indices[0] := AColor;
  end;
 
var LCache : TVertexCache;
    LVertIdxOfs: Integer;
    // In our example we have 3 polygons/quads
    LPolys  : Array[0..2] of TPolygonID;
    I : Integer;
begin
   // Create the mesh object and add it to the supplied owner
  Result := TMeshDef.Create(AOwner);
  Result.Id := 'CustomMesh1';
  Result.FaceOrientation := TFaceOrientation.TwoFace;
  AOwner.AddMesh(Result);
 
  // Setup a vertex cache
  LCache := TVertexCache.Create();
  try
    LCache.UseBuffersAsArray := true;
 
    // Start pushing vertex data into
    LCache.SetPositionSource([
        // quad #1 (vertex#0, vertex#1, vertex#2, vertex#3)
        -0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0,
        // quad #2 (vertex#4, vertex#5, vertex#6, vertex#7)
        -0.5, -1.5, 0, 0.5, -1.5, 0, 0.5, -0.75, 0, -0.5, -0.75, 0,
        // quad #3 (vertex#8, vertex#9, vertex#10, vertex#11)
        -1.5, -1.5, 0, -0.75, -1.5, 0, -0.75, -0.75, 0, -1.5, -0.75, 0
        ]);
 
    // All vertices should have the same normal and tangent vector
    LCache.SetNormalSource([0, 0, -1]);
    LCache.SetNormalSource([1, 0, 0]);
 
    // Each triangle should have its own color
    LCache.SetColorSource(0, [1, 0, 0, 1,   0, 1, 0, 1,   0, 0, 1, 1]);
 
    // You can work with offsets on the cache - very useful for meshes using the same vertex data
    LVertIdxOfs := 0;
 
    // Prepare vertex data indices (from buffers) to add quads
    // At first let's clear the triangle vertex data index structures
    System.SetLength(LPolys[0], 4);
    System.SetLength(LPolys[1], 4);
    System.SetLength(LPolys[2], 4);
 
    // Polygon/Quad #1 - Vertex #0, #1, #2, #3 (with its own color)
    LPolys[0][0] := GetVertexID(0, 0, 0, 0);
    LPolys[0][1] := GetVertexID(1, 0, 0, 0);
    LPolys[0][2] := GetVertexID(2, 0, 0, 0);
    LPolys[0][3] := GetVertexID(3, 0, 0, 0);    
 
    // Polygon/Quad #2 - Vertex #4, #5, #6, #7 (with its own color)
    LPolys[1][0] := GetVertexID(4, 0, 0, 1);
    LPolys[1][1] := GetVertexID(5, 0, 0, 1);
    LPolys[1][2] := GetVertexID(6, 0, 0, 1);
    LPolys[1][3] := GetVertexID(7, 0, 0, 1);
 
    // Polygon/Quad #3 - Vertex #8, #9, #10, #11 (with its own color)
    LPolys[2][0] := GetVertexID(8, 0, 0, 2);
    LPolys[2][1] := GetVertexID(9, 0, 0, 2);
    LPolys[2][2] := GetVertexID(10, 0, 0, 2);
    LPolys[2][3] := GetVertexID(11, 0, 0, 2);
 
    // Add the polygons one by one - with automatic triangulation
    for I := Low(LPolys) to High(LPolys) do
      LVertIdxOfs := LCache.AddPolygon(LVertIdxOfs, LPolys[I], LCache.Vertices, LCache.Indices);
 
    // Load the TMeshDef (Result) from the cache
    TGorillaLoader.LoadDefFromData(Result, LCache);
 
    // When vertex data is added from cache, the vertex index map may be also produced automatically
    // We should add it to the mesh for correct vertex mapping during animations
    Result.AddVertexIndexMap(LCache.IndexMap);
  finally
    FreeAndNil(LCache);
  end;
end;

Add Hierarchy

Since v1.0 most objects in DefTypes are inherited from TTransformDef.

  • TTransformDef
    • TSegmentDef: placeholder definition in mesh hierarchy to allow meshes to be attached to joints or armatures
    • TArmatureDef: armature/humanoid hierarchy to define a skeleton for animations
    • TJointDef: bone/joint that belongs to an armature and defines a part of a skeleton
    • TSkinDef: structure to define vertex modification based on a controller
    • TCustomMeshDef
      • TMeshDef
      • TVertexGroupDef
      • TModelDef
    • TLightDef
    • TCameraDef

Each TTransformDef object has an “Owner” and “Children” property to setup a hierarchy.

Add or remove objects to / from a parent object by the methods:

  • function AddChild(ATransform : TTransformDef) : Integer;
  • procedure RemoveChild(ATransform : TTransformDef);
  • procedure ClearChildren();
  • procedure AddMesh(const AMesh : TMeshDef); [compatible method to elder implementations, but the same like “AddChild”]

You can find a child object by:

  • function FindChildById(const AId : String; const AThrowException : Boolean) : TTransformDef;

and walk through the hierarchy by:

  • procedure Traverse(ADeepTraverse : Boolean; AProc : TTransformTraverseProc);
CustomModel1.Traverse(
  true,
  function(AOwner : TTransformDef; const AIndex : Integer; ATransf : TTransformDef) : Boolean
  begin
    // do something
    Result := true;
  end
  );

How To Use Vertex Groups

VertexGroups are helper structures referring to an owner mesh, which holds original vertex data and complete indices list, while this structure only holds a separated index source for partially rendering a mesh.

They are used for example in the FBX format for rendering some triangles with a different material than others.

CAUTION: They are not working yet with Skin-/Skeleton- or Vertex-Animations!

class function TUserModelBuilder.AddVertexGroup(AMesh : TMeshDef; AMaterial : TMaterialDef): TVertexGroupDef;
begin
  // Create the vertex group and add it as child to an owner mesh, that contains 
  // all vertex data
  Result := TVertexGroupDef.Create(AMesh);
  Result.Id := 'VertexGroup1';
  AMesh.AddMesh(Result);
 
  // Add unique indices, on which vertices to be rendered (render only triangle #1)
  Result.IndexSource.AppendIndices([0,1,2]);
 
  // Link a material to render this group differently
  Result.SetMaterialReference(AMaterial);
end;

Add A Material

To expand the dynamic model setup, let's add a material to our sub-mesh.

uses
  Gorilla.AssetsManager;
 
class function TUserModelBuilder.AddMaterial(AModel : TModelDef; AMesh : TMeshDef; AId : String; AFileName : String) : TMaterialDef;
var LTexDef : TTextureDef;
    LAsset  : TGorillaTextureAsset;
begin
  // Materials are attached to the model to reuse them in multiple meshes
  Result := TMaterialDef.Create(AModel, AId);
  Result.Kind := TMaterialDefKind.mkLambert;
 
  // Create a texture for the material
  // 1) Create a standalone asset (possible to request one from an assets package too)
  LAsset := TGorillaTextureAsset.Create(nil);
  LAsset.ImportFromFile(AFileName);
 
  // 2) Add a diffuse texture in the material
  LTexDef := Result.AddTexture(TColorChannelDef.Diffuse, LAsset, true);
 
  // Finally we need to link it to our mesh
  AMesh.SetMaterialReference(Result);
end;

Add A Simple Animation

Adding animations can become a very challenging part due to complexity.

Not only an animation definition needs to be defined, but also stages, interpolators + armatures and joints for skeleton/skin animations need to be added and configured.

class function TUserModelBuilder.AddAnimation(AModel : TModelDef; AMesh : TMeshDef; AId : String) : TAnimationDef;
var LStage : TAnimationStageDef;
    LInterp : TPoint3DInterpolatorDef;
begin
  // Let's start with the animation main object
  Result := TAnimationDef.Create(AModel);
  Result.Id := 'CustomAnimation1';
  AModel.AddAnimation(Result);
 
  // Each animation should contain at least one stage definition
  LStage := TAnimationStageDef.Create(Result);
  LStage.Id := 'CustomMesh1Stage';
  LStage.Reference := AMesh;
  Result.AddStage(LStage);
 
  // In our example we simply want to move the mesh
  // So we need to create a Point3D interpolator in the stage
  LInterp := TPoint3DInterpolatorDef.Create(LStage);
  LInterp.Id := 'CustomMesh1PositionInterpolator';
  LStage.AddInterpolator(LInterp);
 
  // Configure the animation  
  LInterp.Path := 'Position.Point';
  LInterp.Loop := true;
  LInterp.Duration := 3.0; // 3 seconds
 
  // To move the mesh, we need to add keys to the interpolator
  LInterp.AddKey(0, Point3D(0, 0, 0), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(0.75, Point3D(0, -0.75, 0), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(1, Point3D(0, -1, 0), TAnimationKeyInterpolationType.Linear);
end;

You can add multiple interpolators per stage, but keep in mind it may slow down performance.

We do support different interpolator types, depending on your needs:

InterpolatorDescr
TPoint3DInterpolatorDefTranslation or Scaling interpolator by TPoint3D value to transform a stage reference property.
TVector3DInterpolatorDefRotation interpolator by TVector3D value to transform a stage reference RotationAngle property.
TQuaternion3DInterpolatorDefRotation interpolator by TQuaternion3D value to transform a stage reference RotationAngle property.
TTransformationInterpolatorDefTransformation interpolator by TMatrix3D values to transform the stage reference object by position, rotation and scaling.
TMeshInterpolatorDefVertex position interpolator for direct vertex manipulation of mesh reference.

Add Armature & SkinController

To make skin- or skeleton-animations possible you will need an armature with a hierarchy of joints representing the internal skeleton of a body.

class function TUserModelBuilder.AddArmatureAndController(AModel : TModelDef; AMesh : TMeshDef) : TArmatureDef;
var LJt1,
    LJt2   : TJointDef;
    LCtrl  : TControllerDef;
    LJtRef : TJointRefDef;
    LRes   : Integer;
begin
  //
  // Create an armature definition and link it to the model
  //
  Result := TArmatureDef.Create(AModel);
  Result.Id := 'Armature1';
  AModel.AddArmature(Result);
 
  // Afterwards you can add joints / bones for a hierarchy
  // Starting with the root joint
  Result.Root := TJointDef.Create(Result);
  Result.Root.Id := 'SkeletonRoot';
 
  // Then add the first child node with position offset
  LJt1 := TJointDef.Create(Result);
  LJt1.Id := 'Bone1';
  LJt1.Transform := TMatrix3D.CreateTranslation(Point3D(0, -1, 0));
  Result.Root.AddJoint(LJt1);
 
  // And another child node with position offset
  LJt2 := TJointDef.Create(Result);
  LJt2.Id := 'Bone2';
  LJt2.Transform := TMatrix3D.CreateTranslation(Point3D(-1, 0, 0));
  LJt1.AddJoint(LJt2);
 
  //
  // After we have setup the armature with a bone hierarchy, we can create a skin-controller
  //
  LCtrl := TControllerDef.Create(AMesh);
  LCtrl.Id := 'CustomMesh1Controller';
 
  // Add the controller to the model controllers list
  AModel.AddController(LCtrl);
 
  // Link the controller also to the armature
  Result.AddController(LCtrl);
 
  // Add a skin to our controller -  capable to manage deformation data depending on an armature hierarchy
  LCtrl.Skin := TSkinDef.Create(LCtrl);
 
  // Add the skeleton root reference joint (standalone reference)
  LJtRef := TJointRefDef.Create(Result.Root, LCtrl, true);
  LCtrl.Skin.Skeletons.Add(LJtRef);
 
  // We have to link all relevant joint to the skin and add weights and vertex-indices
  // "Weights" and "Indices" arrays has to have the same size.
  // The sum of all weights (over all joints) for a specific vertex has to be 1.0!
  LJtRef := LCtrl.Skin.LinkJoint(Result.Root);
  LJtRef.Weights := [0.75, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25, 0.25, 0, 0, 0, 0];
  LJtRef.Indices := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
 
  LJtRef := LCtrl.Skin.LinkJoint(LJt1);
  LJtRef.Weights := [0.25, 0.25, 0.25, 0.25, 0.75, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25, 0.25];
  LJtRef.Indices := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
 
  LJtRef := LCtrl.Skin.LinkJoint(LJt2);
  LJtRef.Weights := [0, 0, 0, 0, 0.25, 0.25, 0.25, 0.25, 0.75, 0.75, 0.75, 0.75];
  LJtRef.Indices := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
 
  // Verify the setup
  LRes := LCtrl.Skin.CheckMeshVerticesToBeAttachedCorrectly();
  case LRes of
    0 : ; // everything is okey
    1 : raise Exception.Create('skin vertex not attached');
    2 : raise Exception.Create('skin vertex not attached completely, meaning that total weight value is not equal to 1.0');
    3 : raise Exception.Create('no mesh linked');
    4 : raise Exception.Create('mesh vertex list is empty');
    5 : raise Exception.Create('no skeleton root found');
    6 : raise Exception.Create('no joints linked');
    else raise Exception.Create('unexpected failure');
  end;
end;

Add a Skin-Animation

To animate the armature it's necessary to have compatible animation stages and interpolators.

Related to our armature and bones (joints) before, the code could look something like this:

class function TUserModelBuilder.AddSkinAnimation(AModel : TModelDef;
  AArmature : TArmatureDef; AId : String) : TAnimationDef;
var LStage : TAnimationStageDef;
    LInterp : TPoint3DInterpolatorDef;
begin
  // Let's start with the animation main object
  Result := TAnimationDef.Create(AModel);
  Result.Id := 'CustomAnimation1';
  AModel.AddAnimation(Result);
 
  // ROOT JOINT
  // Each animation should contain at least one stage definition
  LStage := TAnimationStageDef.Create(Result);
  LStage.Id := 'RootBoneStage';
  LStage.Reference := AArmature.Root;
  Result.AddStage(LStage);
 
  // In our example we simply want to move the mesh
  // So we need to create a Point3D interpolator in the stage
  LInterp := TPoint3DInterpolatorDef.Create(LStage);
  LInterp.Id := 'RootBonePosition';
  LStage.AddInterpolator(LInterp);
 
  // Configure the animation
  LInterp.Path := 'Position.Point';
  LInterp.Loop := true;
  LInterp.Duration := 3.0; // 3 seconds
 
  // To move the mesh, we need to add keys to the interpolator
  LInterp.AddKey(0, Point3D(0, 0, 0), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(0.75, Point3D(0, -0.75, 0), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(1, Point3D(0, -1, 0), TAnimationKeyInterpolationType.Linear);
 
  // JOINT1
  LStage := TAnimationStageDef.Create(Result);
  LStage.Id := 'Bone1Stage';
  LStage.Reference := AArmature.Root.FindJointById('Bone1');
  Result.AddStage(LStage);
 
  // In our example we simply want to move the mesh
  // So we need to create a Point3D interpolator in the stage
  LInterp := TPoint3DInterpolatorDef.Create(LStage);
  LInterp.Id := 'Bone1Rotation';
  LStage.AddInterpolator(LInterp);
 
  // Configure the animation
  LInterp.Path := 'RotationAngle.Point';
  LInterp.Loop := true;
  LInterp.Duration := 3.0; // 3 seconds
 
  // To move the mesh, we need to add keys to the interpolator
  LInterp.AddKey(0, Point3D(0, 0, 0), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(0.75, Point3D(0, 90, 0), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(1, Point3D(0, 180, 0), TAnimationKeyInterpolationType.Linear);
 
  // JOINT2
  LStage := TAnimationStageDef.Create(Result);
  LStage.Id := 'Bone2Stage';
  LStage.Reference := AArmature.Root.FindJointById('Bone2');
  Result.AddStage(LStage);
 
  // In our example we simply want to move the mesh
  // So we need to create a Point3D interpolator in the stage
  LInterp := TPoint3DInterpolatorDef.Create(LStage);
  LInterp.Id := 'Bone2Rotation';
  LStage.AddInterpolator(LInterp);
 
  // Configure the animation
  LInterp.Path := 'RotationAngle.Point';
  LInterp.Loop := true;
  LInterp.Duration := 3.0; // 3 seconds
 
  // To move the mesh, we need to add keys to the interpolator
  LInterp.AddKey(0, Point3D(0, 0, 0), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(0.75, Point3D(0, 0, -90), TAnimationKeyInterpolationType.Linear);
  LInterp.AddKey(1, Point3D(0, 0, -180), TAnimationKeyInterpolationType.Linear);
end;

NOTICE: You will need a stage for each joint to be modified during the animation.

Create FMX Components from Definition

After we have build our TModelDef object, we can visualize it easily by pushing it into the TGorillaModel.LoadNewModelFromDef() method. It will take care of all necessary actions and will automatically build all FMX components for you.

procedure TForm1.FormCreate(Sender: TObject);
var LModelDef : TModelDef;
begin
  // Create abstract model data
  LModelDef := TUserModelBuilder.Build();
 
  // Create visual component for the abstract model data
  FModel := TGorillaModel.LoadNewModelFromDef(GorillaViewport1, LModelDef, []);
  FModel.Parent := GorillaViewport1;
 
  // For debugging purposes show the bones
  FModel.ShowJoints := true;
 
  // Run our skin animation
  FModel.AnimationManager.Current.Start;
end;