// Update animation node = compute bones positions and orientations from the track.
        public override void Update(ref MyAnimationUpdateData data)
        {
            // allocate result array from pool
            data.BonesResult = data.Controller.ResultBonesPool.Alloc();
            if (m_animationClip != null && m_animationClip.Bones != null)
            {
                // if necessary, then rebuild bone indices mapping
                if (m_boneIndicesMapping == null)
                {
                    RebuildBoneIndices(data.CharacterBones);
                    Debug.Assert(m_boneIndicesMapping != null);
                }

                // advance local time
                if (!ProcessLayerTimeSync(ref data) &&
                    m_timeAdvancedOnFrameNum != data.Controller.FrameCounter)    // time advances only if it has not already advanced
                {
                    m_timeAdvancedOnFrameNum = data.Controller.FrameCounter;
                    m_localTime += data.DeltaTimeInSeconds * Speed;
                    if (m_loop)
                    {
                        while (m_localTime >= m_animationClip.Duration)
                        {
                            m_localTime -= m_animationClip.Duration;
                        }
                        while (m_localTime < 0)
                        {
                            m_localTime += m_animationClip.Duration;
                        }
                    }
                    else
                    {
                        if (m_localTime >= m_animationClip.Duration)
                        {
                            m_localTime = m_animationClip.Duration;
                        }
                        else if (m_localTime < 0)
                        {
                            m_localTime = 0;
                        }
                    }
                }

                // update indices of keyframes (for every bone)
                UpdateKeyframeIndices();
                // ok, compute for every bone
                for (int i = 0; i < m_animationClip.Bones.Count; i++)
                {
                    var currentBone = m_animationClip.Bones[i];
                    // two keyframe indices to be blended together (i, i+1)
                    // blending N-1 to 0 if looped, staying on the last frame if not looped
                    var currentKeyFrameIndex  = m_currentKeyframes[i];
                    var currentKeyFrameIndex2 = currentKeyFrameIndex + 1;
                    if (currentKeyFrameIndex2 >= currentBone.Keyframes.Count) // safety
                    {
                        currentKeyFrameIndex2 = Math.Max(0, currentBone.Keyframes.Count - 1);
                    }

                    int charBoneIndex = m_boneIndicesMapping[i];                                                                    // use mapping clip bone -> char bone
                    if (charBoneIndex < 0 || charBoneIndex >= data.BonesResult.Count || data.LayerBoneMask[charBoneIndex] == false) // unaffected bone?
                    {
                        continue;
                    }

                    if (currentKeyFrameIndex != currentKeyFrameIndex2 && m_interpolate)
                    {
                        // interpolate between two keyframes
                        var keyframe1 = currentBone.Keyframes[currentKeyFrameIndex];
                        var keyframe2 = currentBone.Keyframes[currentKeyFrameIndex2];

                        float t = (float)((m_localTime - keyframe1.Time) * keyframe2.InvTimeDiff);
                        t = MathHelper.Clamp(t, 0, 1);
                        Quaternion.Slerp(ref keyframe1.Rotation, ref keyframe2.Rotation, t, out data.BonesResult[charBoneIndex].Rotation);
                        Vector3.Lerp(ref keyframe1.Translation, ref keyframe2.Translation, t, out data.BonesResult[charBoneIndex].Translation);
                    }
                    else if (currentBone.Keyframes.Count != 0)
                    {
                        // just copy keyframe, because currentKeyFrameIndex == currentKeyFrameIndex2
                        data.BonesResult[charBoneIndex].Rotation    = currentBone.Keyframes[currentKeyFrameIndex].Rotation;
                        data.BonesResult[charBoneIndex].Translation = currentBone.Keyframes[currentKeyFrameIndex].Translation;
                    }
                    else
                    {
                        // zero keyframe count -> rest pose, leave it be (it is there from allocation)
                    }
                }
            }

            // debug going through animation tree
            data.AddVisitedTreeNodesPathPoint(-1);  // finishing this node, we will go back to parent
        }
        // Update this node. Mixes two nodes from the interval.
        public override void Update(ref MyAnimationUpdateData data)
        {
            // basic check, we do not expect these values to be null, but you never know...
            Debug.Assert(data.Controller != null && data.Controller.Variables != null);
            if (ChildMappings.Count == 0) // no child nodes, no result
            {
                // debug going through animation tree
                data.AddVisitedTreeNodesPathPoint(-1);  // we will go back to parent
                // m_lastKnownFrameCounter = data.Controller.FrameCounter; // do NOT set last known frame counter here, this is invalid state
                return;
            }

            float parameterValue = ComputeParamValue(ref data);

            int index1 = -1;

            for (int i = ChildMappings.Count - 1; i >= 0; i--) // we expect ChildMappings.Count to be less than 5, for loop is ok
            {
                if (ChildMappings[i].ParamValueBinding <= parameterValue)
                {
                    index1 = i;
                    break;
                }
            }

            if (index1 == -1) // simply copy first one
            {
                if (ChildMappings[0].Child != null)
                {
                    // debug going through animation tree
                    data.AddVisitedTreeNodesPathPoint(1);  // we will go to first child

                    ChildMappings[0].Child.Update(ref data);
                    PushLocalTimeToSlaves(0);
                }
                else
                {
                    data.BonesResult = data.Controller.ResultBonesPool.Alloc(); // else return bind pose
                }
            }
            else if (index1 == ChildMappings.Count - 1) // simply copy last one
            {
                if (ChildMappings[index1].Child != null)
                {
                    // debug going through animation tree
                    data.AddVisitedTreeNodesPathPoint(ChildMappings.Count - 1 + 1);  // we will go to last child

                    ChildMappings[index1].Child.Update(ref data);
                    PushLocalTimeToSlaves(index1);
                }
                else
                {
                    data.BonesResult = data.Controller.ResultBonesPool.Alloc(); // else return bind pose
                }
            }
            else // blend between two
            {
                int   index2           = index1 + 1;
                float paramBindingDiff = ChildMappings[index2].ParamValueBinding - ChildMappings[index1].ParamValueBinding;
                float t = (parameterValue - ChildMappings[index1].ParamValueBinding) / paramBindingDiff; // division here, improve me!
                if (t > 0.5f)                                                                            // dominant index will always be index1
                {
                    index1++;
                    index2--;
                    t = 1.0f - t;
                }
                if (t < 0.001f)
                {
                    t = 0.0f;
                }
                else if (t > 0.999f)
                {
                    t = 1.0f;
                }

                var child1 = ChildMappings[index1].Child;
                var child2 = ChildMappings[index2].Child;

                // first (its weight > 0.5) node, dominant
                if (child1 != null && t < 1.0f)
                {
                    // debug going through animation tree
                    data.AddVisitedTreeNodesPathPoint(index1 + 1);  // we will go to dominant child

                    child1.Update(ref data);
                    PushLocalTimeToSlaves(index1);
                }
                else
                {
                    data.BonesResult = data.Controller.ResultBonesPool.Alloc();
                }

                // second (its weight < 0.5) node
                MyAnimationUpdateData animationUpdateData2 = data; // local copy for second one
                if (child2 != null && t > 0.0f)
                {
                    animationUpdateData2.DeltaTimeInSeconds = 0.0;                 // driven by dominant child 1, do not change you time yourself
                    // debug going through animation tree
                    animationUpdateData2.AddVisitedTreeNodesPathPoint(index2 + 1); // we will go to dominant child

                    child2.Update(ref animationUpdateData2);
                    data.VisitedTreeNodesCounter = animationUpdateData2.VisitedTreeNodesCounter;
                }
                else
                {
                    animationUpdateData2.BonesResult = animationUpdateData2.Controller.ResultBonesPool.Alloc();
                }

                // and now blend
                for (int j = 0; j < data.BonesResult.Count; j++)
                {
                    if (data.LayerBoneMask[j]) // mix only bones affected by current layer
                    {
                        data.BonesResult[j].Rotation = Quaternion.Slerp(data.BonesResult[j].Rotation,
                                                                        animationUpdateData2.BonesResult[j].Rotation, t);
                        data.BonesResult[j].Translation = Vector3.Lerp(data.BonesResult[j].Translation,
                                                                       animationUpdateData2.BonesResult[j].Translation, t);
                    }
                }
                // and deallocate animationUpdateData2.BonesResult since we no longer need it
                data.Controller.ResultBonesPool.Free(animationUpdateData2.BonesResult);
            }

            // debug going through animation tree
            m_lastKnownFrameCounter = data.Controller.FrameCounter;
            data.AddVisitedTreeNodesPathPoint(-1);  // we will go back to parent
        }
        // Update this node. Mixes two nodes from the interval.
        public override void Update(ref MyAnimationUpdateData data)
        {
            // basic check, we do not expect these values to be null, but you never know...
            Debug.Assert(data.Controller != null && data.Controller.Variables != null);
            if (ChildMappings.Count == 0) // no child nodes, no result
            {
                // debug going through animation tree
                data.AddVisitedTreeNodesPathPoint(-1);  // we will go back to parent
                // m_lastKnownFrameCounter = data.Controller.FrameCounter; // do NOT set last known frame counter here, this is invalid state
                return;
            }

            float parameterValue = ComputeParamValue(ref data);

            int index1 = -1;
            for (int i = ChildMappings.Count - 1; i >= 0; i--) // we expect ChildMappings.Count to be less than 5, for loop is ok
                if (ChildMappings[i].ParamValueBinding <= parameterValue)
                {
                    index1 = i;
                    break;
                }

            if (index1 == -1) // simply copy first one
            {
                if (ChildMappings[0].Child != null)
                {
                    // debug going through animation tree
                    data.AddVisitedTreeNodesPathPoint(1);  // we will go to first child

                    ChildMappings[0].Child.Update(ref data);
                    PushLocalTimeToSlaves(0);
                }
                else
                    data.BonesResult = data.Controller.ResultBonesPool.Alloc(); // else return bind pose
            }
            else if (index1 == ChildMappings.Count - 1) // simply copy last one
            {
                if (ChildMappings[index1].Child != null)
                {
                    // debug going through animation tree
                    data.AddVisitedTreeNodesPathPoint(ChildMappings.Count - 1 + 1);  // we will go to last child

                    ChildMappings[index1].Child.Update(ref data);
                    PushLocalTimeToSlaves(index1);
                }
                else
                    data.BonesResult = data.Controller.ResultBonesPool.Alloc(); // else return bind pose
            }
            else // blend between two
            {
                int index2 = index1 + 1;
                float paramBindingDiff = ChildMappings[index2].ParamValueBinding - ChildMappings[index1].ParamValueBinding;
                float t = (parameterValue - ChildMappings[index1].ParamValueBinding) / paramBindingDiff; // division here, improve me!
                if (t > 0.5f) // dominant index will always be index1
                {
                    index1++;
                    index2--;
                    t = 1.0f - t;
                }
                if (t < 0.001f)
                    t = 0.0f;
                else if (t > 0.999f)
                    t = 1.0f;

                var child1 = ChildMappings[index1].Child;
                var child2 = ChildMappings[index2].Child;

                // first (its weight > 0.5) node, dominant
                if (child1 != null && t < 1.0f)
                {
                    // debug going through animation tree
                    data.AddVisitedTreeNodesPathPoint(index1 + 1);  // we will go to dominant child

                    child1.Update(ref data);
                    PushLocalTimeToSlaves(index1);
                }
                else
                    data.BonesResult = data.Controller.ResultBonesPool.Alloc();

                // second (its weight < 0.5) node
                MyAnimationUpdateData animationUpdateData2 = data; // local copy for second one
                if (child2 != null && t > 0.0f)
                {
                    animationUpdateData2.DeltaTimeInSeconds = 0.0; // driven by dominant child 1, do not change you time yourself
                    // debug going through animation tree
                    animationUpdateData2.AddVisitedTreeNodesPathPoint(index2 + 1);  // we will go to dominant child

                    child2.Update(ref animationUpdateData2);
                    data.VisitedTreeNodesCounter = animationUpdateData2.VisitedTreeNodesCounter;
                }
                else
                    animationUpdateData2.BonesResult = animationUpdateData2.Controller.ResultBonesPool.Alloc();

                // and now blend
                for (int j = 0; j < data.BonesResult.Count; j++)
                {
                    if (data.LayerBoneMask[j]) // mix only bones affected by current layer
                    {
                        data.BonesResult[j].Rotation = Quaternion.Slerp(data.BonesResult[j].Rotation,
                            animationUpdateData2.BonesResult[j].Rotation, t);
                        data.BonesResult[j].Translation = Vector3.Lerp(data.BonesResult[j].Translation,
                            animationUpdateData2.BonesResult[j].Translation, t);
                    }
                }
                // and deallocate animationUpdateData2.BonesResult since we no longer need it
                data.Controller.ResultBonesPool.Free(animationUpdateData2.BonesResult);
            }

            // debug going through animation tree
            m_lastKnownFrameCounter = data.Controller.FrameCounter;
            data.AddVisitedTreeNodesPathPoint(-1);  // we will go back to parent
        }
        // Update animation node = compute bones positions and orientations from the track. 
        public override void Update(ref MyAnimationUpdateData data)
        {
            // allocate result array from pool
            data.BonesResult = data.Controller.ResultBonesPool.Alloc();
            if (m_animationClip != null && m_animationClip.Bones != null)
            {
                // if necessary, then rebuild bone indices mapping
                if (m_boneIndicesMapping == null)
                {
                    RebuildBoneIndices(data.CharacterBones);
                    Debug.Assert(m_boneIndicesMapping != null);
                }

                // advance local time
                if (!ProcessLayerTimeSync(ref data))
                {
                    m_localTime += data.DeltaTimeInSeconds * Speed;
                    if (m_loop)
                    {
                        while (m_localTime >= m_animationClip.Duration)
                            m_localTime -= m_animationClip.Duration;
                        while (m_localTime < 0)
                            m_localTime += m_animationClip.Duration;
                    }
                    else
                    {
                        if (m_localTime >= m_animationClip.Duration)
                        {
                            m_localTime = m_animationClip.Duration;
                        }
                        else if (m_localTime < 0)
                        {
                            m_localTime = 0;
                        }
                    }
                }

                // update indices of keyframes (for every bone)
                UpdateKeyframeIndices();
                // ok, compute for every bone
                for (int i = 0; i < m_animationClip.Bones.Count; i++)
                {
                    var currentBone = m_animationClip.Bones[i];
                    // two keyframe indices to be blended together (i, i+1)
                    // blending N-1 to 0 if looped, staying on the last frame if not looped
                    var currentKeyFrameIndex = m_currentKeyframes[i];
                    var currentKeyFrameIndex2 = currentKeyFrameIndex + 1;
                    if (currentKeyFrameIndex2 >= currentBone.Keyframes.Count) // safety
                        currentKeyFrameIndex2 = Math.Max(0, currentBone.Keyframes.Count - 1);

                    int charBoneIndex = m_boneIndicesMapping[i]; // use mapping clip bone -> char bone
                    if (charBoneIndex < 0 || charBoneIndex >= data.BonesResult.Count || data.LayerBoneMask[charBoneIndex] == false) // unaffected bone?
                        continue;

                    if (currentKeyFrameIndex != currentKeyFrameIndex2 && m_interpolate)
                    {
                        // interpolate between two keyframes
                        var keyframe1 = currentBone.Keyframes[currentKeyFrameIndex];
                        var keyframe2 = currentBone.Keyframes[currentKeyFrameIndex2];

                        float t = (float)((m_localTime - keyframe1.Time) * keyframe2.InvTimeDiff);
                        t = MathHelper.Clamp(t, 0, 1);
                        Quaternion.Slerp(ref keyframe1.Rotation, ref keyframe2.Rotation, t, out data.BonesResult[charBoneIndex].Rotation);
                        Vector3.Lerp(ref keyframe1.Translation, ref keyframe2.Translation, t, out data.BonesResult[charBoneIndex].Translation);
                    }
                    else if (currentBone.Keyframes.Count != 0)
                    {
                        // just copy keyframe, because currentKeyFrameIndex == currentKeyFrameIndex2
                        data.BonesResult[charBoneIndex].Rotation = currentBone.Keyframes[currentKeyFrameIndex].Rotation;
                        data.BonesResult[charBoneIndex].Translation = currentBone.Keyframes[currentKeyFrameIndex].Translation;
                    }
                    else
                    {
                        // zero keyframe count -> rest pose, leave it be (it is there from allocation)
                    }
                }
            }

            // debug going through animation tree
            data.AddVisitedTreeNodesPathPoint(-1);  // finishing this node, we will go back to parent
        }