Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Showcase Dynamically Generated Constraints Free Code

Discussion in 'DOTS Animation' started by NT_Ninetails, May 19, 2021.

  1. NT_Ninetails

    NT_Ninetails

    Joined:
    Jan 21, 2018
    Posts:
    196
    I made this code that allows you to easily add constraints to your animation. It is not done yet (gotta add weight changing and some other things) but i wanted to share it to help people get a better understanding on how it works.

    Ignore some of the slightly broken animation, that was an old version of a custom animation system i made.

    NOTE: this demo was executed in the Unity.Animation Sample Project.

    For reference, take a look at the Unity.Animation Constraints example.



    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Animation;
    3. using Unity.DataFlowGraph;
    4. using Unity.Entities;
    5. using Unity.Mathematics;
    6. using static ConstraintsSample.ConstraintGraphComponent;
    7. using System.Collections.Generic;
    8. #if UNITY_EDITOR
    9. using Unity.Animation.Hybrid;
    10.  
    11. namespace Game.Animation {
    12.     public class DynamicAnimationConstraints : MonoBehaviour, IConvertGameObjectToEntity
    13.     {
    14.         public List<TwoBoneIKSetup> TwoBoneConstraints;
    15.         public List<AimConstraintSetup> AimConstraints;
    16.  
    17.         int3 Convert(in TwoBoneIKSetup setup, RigComponent rig)
    18.         {
    19.             var indices = math.int3(
    20.                 RigGenerator.FindTransformIndex(setup.Root, rig.Bones),
    21.                 RigGenerator.FindTransformIndex(setup.Mid, rig.Bones),
    22.                 RigGenerator.FindTransformIndex(setup.Tip, rig.Bones)
    23.             );
    24.  
    25.             if (indices.x == -1 || indices.y == -1 || indices.z == -1)
    26.                 throw new System.InvalidOperationException("Invalid TwoBoneIK transforms");
    27.  
    28.             return indices;
    29.         }
    30.  
    31.         int Convert(in AimConstraintSetup setup, RigComponent rig)
    32.         {
    33.             var index = RigGenerator.FindTransformIndex(setup.Bone, rig.Bones);
    34.             if (index == -1)
    35.                 throw new System.InvalidOperationException("Invalid AimConstraint Bone");
    36.  
    37.             return index;
    38.         }
    39.  
    40.         public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    41.         {
    42.             var rig = GetComponent<RigComponent>();
    43.             int3[] IKDatas = new int3[0];
    44.             Entity[] TargetEntities = new Entity[0];
    45.             float3[] AimAxis = new float3[0];
    46.             int[] HeadIndex = new int[0];
    47.  
    48.             if (TwoBoneConstraints.Count > 0)
    49.             {
    50.                 IKDatas = new int3[TwoBoneConstraints.Count];
    51.                 TargetEntities = new Entity[TwoBoneConstraints.Count];
    52.                 for (int i = 0; i < TwoBoneConstraints.Count; i++)
    53.                 {
    54.                     if (TwoBoneConstraints[i].Target == null) UnityEngine.Debug.LogError("Constraint has no Target!");
    55.                     else
    56.                     {
    57.                         IKDatas[i] = Convert(TwoBoneConstraints[i], rig);
    58.                         TargetEntities[i] = conversionSystem.GetPrimaryEntity(TwoBoneConstraints[i].Target);
    59.                     }
    60.                 }
    61.             }
    62.             if (AimConstraints.Count > 0)
    63.             {
    64.                 AimAxis = new float3[AimConstraints.Count];
    65.                 HeadIndex = new int[AimConstraints.Count];
    66.                 for (int i = 0; i < AimConstraints.Count; i++)
    67.                 {
    68.                     AimAxis[i] = AimConstraints[i].LocalAimAxis;
    69.                     HeadIndex[i] = Convert(AimConstraints[i], rig);
    70.                 }
    71.             }
    72.  
    73.             BlobAssetReference<DynamicAnimationConstraintIKSetupData> setup = BlobAssetReference<DynamicAnimationConstraintIKSetupData>.Create(
    74.                 new DynamicAnimationConstraintIKSetupData {
    75.                     AimAxis = AimAxis,
    76.                     IKDatas = IKDatas,
    77.                     HeadIndex = HeadIndex,
    78.                     TargetEntities = TargetEntities
    79.                 });
    80.             if (!setup.IsCreated) UnityEngine.Debug.LogError("Failed to create the setup!");
    81.  
    82.             dstManager.AddComponentData(entity, new DynamicAnimationConstraintIKSetup { Value = setup });
    83.             dstManager.AddComponentData(entity, new DynamicAnimationConstraintIKGraphSetup());
    84.  
    85.             dstManager.AddComponent<DeltaTime>(entity);
    86.         }
    87.     }
    88.  
    89. #endif
    90.  
    91.     public struct DynamicAnimationConstraintIKSetup : IComponentData
    92.     {
    93.         public BlobAssetReference<DynamicAnimationConstraintIKSetupData> Value;
    94.     }
    95.     // this is the data that will be stored in a BlobAssetReference (NOTE: this data is not blittable and managed which is why we need the BlobAssetReferece)
    96.     // Why use BlobAssetReference?
    97.     // This data is assumed to be unique (each entity will have different constraints), generated once and doesn't change
    98.     // (though you can still change it technically)
    99.     public struct DynamicAnimationConstraintIKSetupData
    100.     {
    101.         public int3[] IKDatas;
    102.         public Entity[] TargetEntities;
    103.         public float3[] AimAxis;
    104.         public int[] HeadIndex;
    105.     }
    106.     // I changed the way the Constaints are generated so this is really just use to activate the CreateGraph function
    107.     public  struct DynamicAnimationConstraintIKGraphSetup : ISampleSetup
    108.     {
    109.     /*    public int3 LeftArmIK;
    110.         public Entity LeftTargetEntity;
    111.         public int3 RightArmIK;
    112.         public Entity RightTargetEntity;
    113.         public int HeadIndex;
    114.         public float3 HeadLocalAimAxis;*/
    115.     };
    116.     // Constraint data added to the entity
    117.     public struct DynamicAnimationConstraintIKGraphData : ISampleData
    118.     {
    119.         public GraphHandle Graph;
    120.         // Why store the IK Node Data?
    121.         // Since you can call functions on these nodes by sending messages I choose to store this
    122.         // If you don't wish to store the data then just remove this from here and remove the setter in the CreateGraph function
    123.         public BlobAssetReference<DynamicAnimationConstraintIKData> IKData;
    124.     }
    125.  
    126.     public struct DynamicAnimationConstraintIKData
    127.     {
    128.         public NodeHandle<TwoBoneIKNode>[] IKNodes;
    129.         public NodeHandle<AimConstraintNode>[] AimNodes;
    130.     }
    131.  
    132.     // Creates a graph which constrains the left and right arms using a TwoBoneIK constraint and a head lookat using an AimConstraint.
    133.     // This graph is created in the ProcessLateAnimationGraph system which is evaluated after the TransformSystemGroup making it ideal to
    134.     // do pose corrections.
    135.     [UpdateBefore(typeof(DefaultAnimationSystemGroup))]
    136.     public class BlendWithIKGraphSystem : SampleSystemBase<
    137.         DynamicAnimationConstraintIKGraphSetup,
    138.         DynamicAnimationConstraintIKGraphData,
    139.         ProcessLateAnimationGraph
    140.     >
    141.     {
    142.         protected override DynamicAnimationConstraintIKGraphData CreateGraph(Entity entity, ref Rig rig, ProcessLateAnimationGraph graphSystem,
    143.             ref DynamicAnimationConstraintIKGraphSetup setup)
    144.         {
    145.             // get Set and graph
    146.             var set = graphSystem.Set;
    147.             var graph = graphSystem.CreateGraph();
    148.             // create the graph data that will be attached to the entity
    149.             var data = new DynamicAnimationConstraintIKGraphData();
    150.             data.Graph = graph;
    151.             // Creates a component node associated with a GraphHandle. If either the GraphHandle or the animation system NodeSet is disposed, this node
    152.             var entityNode = graphSystem.CreateNode(graph, entity);
    153.  
    154.             //Grab the buffers and create the connections
    155.             EntityManager em = graphSystem.EntityManager;
    156.  
    157.             NodeHandle<TwoBoneIKNode>[] IKNodes = new NodeHandle<TwoBoneIKNode>[0];
    158.             NodeHandle<AimConstraintNode>[] AimNodes = new NodeHandle<AimConstraintNode>[0];
    159.  
    160.             if (em.HasComponent<DynamicAnimationConstraintIKSetup>(entity))
    161.             {
    162.                 var AnimationConstraintIKSetup = em.GetComponentData<DynamicAnimationConstraintIKSetup>(entity);
    163.              /*   UnityEngine.Debug.Log(string.Format("got it! {0},{1},{2},{3}",
    164.                     AnimationConstraintIKSetup.Value.Value.AimAxis.Length,
    165.                     AnimationConstraintIKSetup.Value.Value.HeadIndex.Length,
    166.                     AnimationConstraintIKSetup.Value.Value.IKDatas.Length,
    167.                     AnimationConstraintIKSetup.Value.Value.TargetEntities.Length));
    168.                 */
    169.                 IKNodes = new NodeHandle<TwoBoneIKNode>[AnimationConstraintIKSetup.Value.Value.IKDatas.Length];
    170.                 for (int i = 0; i < AnimationConstraintIKSetup.Value.Value.IKDatas.Length; i++)
    171.                 {
    172.                     if (AnimationConstraintIKSetup.Value.Value.TargetEntities[i].Equals(Entity.Null))
    173.                         UnityEngine.Debug.LogWarning("Given IKSetup has a Invalid Target Entity");
    174.                     else
    175.                     {
    176.                         // Create Nodes
    177.                         var m_TwoBoneIKNode = graphSystem.CreateNode<TwoBoneIKNode>(graph);
    178.                         var m_TargetEntityNode = graphSystem.CreateNode(graph, AnimationConstraintIKSetup.Value.Value.TargetEntities[i]);
    179.                         var m_WorldToRootNode = graphSystem.CreateNode<WorldToRootNode>(graph);
    180.                         // If 0 then connect the Kernal Input as feedback connection
    181.                         if(i == 0) set.Connect(entityNode, m_TwoBoneIKNode, TwoBoneIKNode.KernelPorts.Input, NodeSet.ConnectionType.Feedback);
    182.                         // else connect the output of the previous nodes and the input of the current node
    183.                         else if (i > 0)
    184.                         {
    185.                             set.Connect(IKNodes[i - 1], TwoBoneIKNode.KernelPorts.Output, m_TwoBoneIKNode,
    186.                                 TwoBoneIKNode.KernelPorts.Input);
    187.                         }
    188.                         // setup other connections
    189.                         set.Connect(entityNode, m_WorldToRootNode, WorldToRootNode.KernelPorts.Input,  NodeSet.ConnectionType.Feedback);
    190.                         set.Connect(entityNode, m_WorldToRootNode, WorldToRootNode.KernelPorts.RootEntity, NodeSet.ConnectionType.Feedback);
    191.                         set.Connect(m_TargetEntityNode, m_WorldToRootNode, WorldToRootNode.KernelPorts.LocalToWorldToRemap);
    192.                         set.Connect(m_WorldToRootNode, WorldToRootNode.KernelPorts.Output, m_TwoBoneIKNode, TwoBoneIKNode.KernelPorts.Target);
    193.  
    194.                      
    195.                         // create message to send to IK node
    196.                         var m_IKMessage = new TwoBoneIKNode.SetupMessage
    197.                         {
    198.                             RootIndex = AnimationConstraintIKSetup.Value.Value.IKDatas[i].x,
    199.                             MidIndex = AnimationConstraintIKSetup.Value.Value.IKDatas[i].y,
    200.                             TipIndex = AnimationConstraintIKSetup.Value.Value.IKDatas[i].z,
    201.                             TargetOffset = new RigidTransform(quaternion.identity, float3.zero)
    202.                         };
    203.                         set.SendMessage(m_TwoBoneIKNode, TwoBoneIKNode.SimulationPorts.Rig, rig);
    204.                         set.SendMessage(m_TwoBoneIKNode, TwoBoneIKNode.SimulationPorts.ConstraintSetup, m_IKMessage);
    205.                         set.SendMessage(m_WorldToRootNode, WorldToRootNode.SimulationPorts.Rig, rig);
    206.                         // node created, set to array
    207.                         IKNodes[i] = m_TwoBoneIKNode;
    208.                     }
    209.                 }
    210.  
    211.                 AimNodes = new NodeHandle<AimConstraintNode>[AnimationConstraintIKSetup.Value.Value.HeadIndex.Length];
    212.                 // the AimConstraint needs this stream
    213.                 var stream = AnimationStream.FromDefaultValues(rig);
    214.                 for (int i = 0; i < AnimationConstraintIKSetup.Value.Value.HeadIndex.Length; i++)
    215.                 {
    216.                     //create look node
    217.                     var aimConstraintLookAtNode = graphSystem.CreateNode<AimConstraintNode>(graph);
    218.  
    219.                     // we need to connect the input of the aimConstraintLookAtNode to something.
    220.                     // when i == 0, we use the last created IkNode if there were any created, otherwise
    221.                     // we use a Feedback connection. if i > 0 then just connection the input to the
    222.                     // previous node's output
    223.  
    224.                     if(IKNodes.Length > 0 && i == 0)
    225.                         set.Connect(IKNodes[IKNodes.Length-1], TwoBoneIKNode.KernelPorts.Output, aimConstraintLookAtNode,
    226.                             AimConstraintNode.KernelPorts.Input);
    227.                     else if(i == 0)
    228.                         set.Connect(entityNode, aimConstraintLookAtNode, AimConstraintNode.KernelPorts.Input, NodeSet.ConnectionType.Feedback);
    229.                     else if (i > 0)
    230.                         set.Connect(AimNodes[i - 1], AimConstraintNode.KernelPorts.Output, aimConstraintLookAtNode,
    231.                             AimConstraintNode.KernelPorts.Input);
    232.  
    233.                     var headAim = new AimConstraintNode.SetupMessage
    234.                     {
    235.                         Index = AnimationConstraintIKSetup.Value.Value.HeadIndex[i],
    236.                         LocalAimAxis = AnimationConstraintIKSetup.Value.Value.AimAxis[i],
    237.                         LocalAxesMask = math.bool3(true)
    238.                     };
    239.  
    240.  
    241.                     // Same as above but now applying static corrections to the head bone for a look at
    242.                     set.SendMessage(aimConstraintLookAtNode, AimConstraintNode.SimulationPorts.Rig, rig);
    243.                     set.SendMessage(aimConstraintLookAtNode, AimConstraintNode.SimulationPorts.ConstraintSetup, headAim);
    244.                     set.SetPortArraySize(aimConstraintLookAtNode, AimConstraintNode.KernelPorts.SourcePositions, 1);
    245.                     set.SetPortArraySize(aimConstraintLookAtNode, AimConstraintNode.KernelPorts.SourceOffsets, 1);
    246.                     set.SetPortArraySize(aimConstraintLookAtNode, AimConstraintNode.KernelPorts.SourceWeights, 1);
    247.                     set.SetData(aimConstraintLookAtNode, AimConstraintNode.KernelPorts.SourcePositions, 0,
    248.                         stream.GetLocalToRootTranslation(headAim.Index) + math.float3(0f, -2f, 0.5f));
    249.                     set.SetData(aimConstraintLookAtNode, AimConstraintNode.KernelPorts.SourceOffsets, 0, quaternion.identity);
    250.                     set.SetData(aimConstraintLookAtNode, AimConstraintNode.KernelPorts.SourceWeights, 0, 1f);
    251.                     AimNodes[i] = aimConstraintLookAtNode;
    252.                 }
    253.  
    254.                 // In order for the constraints to be connected we have to connect the "last" constaint node to the entity
    255.                 // the "last" constaint node can be either a AimNode or an IKNode so we have to test for it.
    256.                 if (AimNodes.Length > 0)
    257.                     set.Connect(AimNodes[AimNodes.Length - 1], AimConstraintNode.KernelPorts.Output, entityNode);
    258.                 else if (IKNodes.Length > 0)
    259.                     set.Connect(IKNodes[IKNodes.Length - 1], TwoBoneIKNode.KernelPorts.Output, entityNode);
    260.                 else
    261.                     UnityEngine.Debug.LogWarning("No Constraints Created! Was this Intentional?");
    262.  
    263.  
    264.  
    265.             }
    266.  
    267.             data.IKData = BlobAssetReference<DynamicAnimationConstraintIKData>.Create(new DynamicAnimationConstraintIKData {
    268.                 AimNodes = AimNodes,
    269.                 IKNodes = IKNodes
    270.             });
    271.  
    272.             if (!data.IKData.IsCreated) UnityEngine.Debug.LogError("Failed to create BlobAssetReference!");
    273.             graphSystem.EntityManager.RemoveComponent<DynamicAnimationConstraintIKSetup>(entity);
    274.             return data;
    275.         }
    276.  
    277.         protected override void DestroyGraph(Entity entity, ProcessLateAnimationGraph graphSystem, ref DynamicAnimationConstraintIKGraphData data)
    278.         {
    279.             graphSystem.Dispose(data.Graph);
    280.         }
    281.     }
    282. }
    283.  
    284.  
     
    Last edited: May 19, 2021