====== 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: - buffer all vertex data (position, normals, texture-coordinates, ...) - add triangles/polygons from those === Properties === ^Property ^Descr^ |PositionSource|Buffer for all vertex position coordinates| |NormalSource|Buffer for all vertex normal vectors| |BinormalSource|Buffer for all binormal vectors| |TangentSource|Buffer for all tangent vectors| |TextureSource[Index : Integer]|Buffer for multiple texture coordinates| |ColorSource[Index : Integer]|Buffer for multiple color values| |IndexMap|Some formats need mapping between vertices by index value. Also it is getting filled during auto-triangulation.| |Vertices|List of combined vertex data into a single structure (TVertexData): TList| |Indices|List of arrays with 3 ordinal values representing triangles: TList| === Append Vertex Data === ^Method ^Argument ^Descr^ |AddPositionSource|TSingleDynArray|Add a single vertex position by an array of 3 floating point values representing x, y and z| |AddPositionSource|TPoint3D|Add a single 3D vertex position| |AddNormalSource|TSingleDynArray|Add a single normal vector by an array of 3 floating point values representing x, y and z| |AddNormalSource|TPoint3D|Add a single 3D normal vector| |AddBinormalSource|TSingleDynArray|Add a single binormal vector by an array of 3 floating point values representing x, y and z| |AddBinormalSource|TPoint3D|Add a single 3D binormal vector| |AddTangentSource|TSingleDynArray|Add a single tangent vector by an array of 3 floating point values representing x, y and z| |AddTangentSource|TArray|Add multiple tangent vectors by an array of tangent vectors| |AddTangentSource|TPoint3D|Add a single 3D tangent vector| |AddTextureSource|AIndex, TSingleDynArray, AConvertToUV|Add a single texture coordinate by an array of 2 floating point values representing x, y. AConvertToUV will convert between st and uv coordinates.| |AddTextureSource|AIndex, TPointF, AConvertToUV|Add a single 2D texture coordinate. AConvertToUV will convert between st and uv coordinates.| |AddColorSource|AIndex, TSingleDynArray|Add single vertex color by an array of 4 floating point values representing r,g,b,a| |AddColorSource|AIndex, TAlphaColorF|Add a single color value| === Push Multiple Vertex Data === Pushing multiple vertex data __will always clear the specific buffer__ and __will not append__ the data. ^Method^Argument^Descr^ |SetPositionSource|TSingleDynArray|Adds 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, ...]| |SetNormalSource|TSingleDynArray|Adds 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, ...]| |SetNormalSource|TSingleDynArray, ATransform|Adds 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.| |SetBiNormalSource|TSingleDynArray|Adds 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, ...]| |SetTangentSource|TSingleDynArray|Adds 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, ...]| |SetTextureSource|AIndex, TSingleDynArray, AConvertToUV|Adds 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.| |SetColorSource|AIndex,TSingleDynArray|Adds 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 === ^Method^Descr^ |function AddTriangle(const AIndexOffset : Integer; const ATriangle : TTriangleID; const AVertices : TList; const AIndices : TList; 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; const AIndices : TList; 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: ^Interpolator^Descr^ |TPoint3DInterpolatorDef|Translation or Scaling interpolator by TPoint3D value to transform a stage reference property.| |TVector3DInterpolatorDef|Rotation interpolator by TVector3D value to transform a stage reference RotationAngle property.| |TQuaternion3DInterpolatorDef|Rotation interpolator by TQuaternion3D value to transform a stage reference RotationAngle property.| |TTransformationInterpolatorDef|Transformation interpolator by TMatrix3D values to transform the stage reference object by position, rotation and scaling.| |TMeshInterpolatorDef|Vertex 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. {{:1.0.0:armature.png?400|}} 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;