/// <summary>
        /// Synchronize time with defined layer. Returns false if the time is not synchronized.
        /// </summary>
        private bool ProcessLayerTimeSync(ref MyAnimationUpdateData data)
        {
            if (m_synchronizeWithLayerRef == null)
            {
                if (m_synchronizeWithLayerName == null)
                {
                    return(false);
                }

                m_synchronizeWithLayerRef = data.Controller.GetLayerByName(m_synchronizeWithLayerName);
                if (m_synchronizeWithLayerRef == null)
                {
                    return(false);
                }
            }
            var nodeFromSyncLayer = m_synchronizeWithLayerRef.CurrentNode as MyAnimationStateMachineNode;

            if (nodeFromSyncLayer == null || nodeFromSyncLayer.RootAnimationNode == null)
            {
                return(false);
            }

            SetLocalTimeNormalized(nodeFromSyncLayer.RootAnimationNode.GetLocalTimeNormalized());
            return(true);
        }
Exemple #2
0
        // Update animation state (position and orientation of bones).
        // Called from MySessionComponentAnimationSystem.
        public void Update()
        {
            using (m_componentValidLock.AcquireSharedUsing())
            {
                if (!m_componentValid)
                {
                    return;
                }

                VRageRender.Animations.MyAnimationUpdateData updateData = new VRageRender.Animations.MyAnimationUpdateData();
                updateData.DeltaTimeInSeconds = VRage.Game.MyEngineConstants.UPDATE_STEP_SIZE_IN_SECONDS;
                updateData.CharacterBones     = m_characterBones;
                updateData.Controller         = null; // will be set inside m_controller.Update automatically if null
                updateData.BonesResult        = null; // will be set inside m_controller.Update (result!)

                m_controller.Update(ref updateData);
                if (updateData.BonesResult != null)
                {
                    for (int i = 0; i < updateData.BonesResult.Count; i++)
                    {
                        CharacterBones[i].SetCompleteTransform(ref updateData.BonesResult[i].Translation,
                                                               ref updateData.BonesResult[i].Rotation);
                    }
                }
                m_lastBoneResult = updateData.BonesResult;

#if OFFICIAL_BUILD == false
                var helperLast = m_lastFrameActions;
                m_lastFrameActions    = m_currentFrameActions;
                m_currentFrameActions = helperLast;
                if (m_currentFrameActions != null)
                {
                    m_currentFrameActions.Clear();
                }
#endif
            }
        }
        /// <summary>
        /// Update this animation controller.
        /// </summary>
        /// <param name="animationUpdateData">See commentary in MyAnimationUpdateData</param>
        public void Update(ref MyAnimationUpdateData animationUpdateData)
        {
            FrameCounter++;
            if (animationUpdateData.CharacterBones == null || !ResultBonesPool.IsValid())
                return; // safety
            if (animationUpdateData.Controller == null)
                animationUpdateData.Controller = this;
            ResultBonesPool.FreeAll(); // free all previous bone allocations, return them to pool
                                       // (this is a safe place to do that, nobody should use them by this time)

            Variables.SetValue(MyAnimationVariableStorageHints.StrIdRandomStable, MyRandom.Instance.NextFloat());

            // Set the first layer.
            if (m_layers.Count > 0)
            {
                ResultBonesPool.SetDefaultPose(null);
                m_layers[0].Update(ref animationUpdateData);
            }
            // Other layers can replace some transforms or add them
            for (int i = 1; i < m_layers.Count; i++)
            {
                var currentLayer = m_layers[i];
                MyAnimationUpdateData animationUpdateDataLast = animationUpdateData;
                animationUpdateData.LayerBoneMask = null;
                animationUpdateData.BonesResult = null;
                ResultBonesPool.SetDefaultPose(currentLayer.Mode == MyAnimationStateMachine.MyBlendingMode.Replace 
                    ? animationUpdateDataLast.BonesResult
                    : null);
                m_layers[i].Update(ref animationUpdateData);
                if (animationUpdateData.BonesResult == null 
                    || m_layers[i].CurrentNode == null  // optimization
                    || ((MyAnimationStateMachineNode)m_layers[i].CurrentNode).RootAnimationNode == null // optimization
                    || ((MyAnimationStateMachineNode)m_layers[i].CurrentNode).RootAnimationNode is MyAnimationTreeNodeDummy) // optimization
                {
                    animationUpdateData = animationUpdateDataLast; // restore backup
                    continue;
                }

                int boneCount = animationUpdateData.BonesResult.Count;
                var bonesLast = animationUpdateDataLast.BonesResult;
                var bones = animationUpdateData.BonesResult;
                var bonesBindPose = animationUpdateDataLast.CharacterBones;

                if (currentLayer.Mode == MyAnimationStateMachine.MyBlendingMode.Replace)
                {
                    for (int j = 0; j < boneCount; j++)
                    {
                        if (animationUpdateData.LayerBoneMask[j] == false)
                        {
                            bones[j].Translation = bonesLast[j].Translation;
                            bones[j].Rotation = bonesLast[j].Rotation;
                        }
                    }
                }
                else if (currentLayer.Mode == MyAnimationStateMachine.MyBlendingMode.Add)
                {
                    for (int j = 0; j < boneCount; j++)
                    {
                        if (animationUpdateData.LayerBoneMask[j])
                        {
                            // add result to current
                            Vector3 addTranslation;
                            Quaternion addRotation;
                            bonesBindPose[j].GetCompleteTransform(ref bones[j].Translation, ref bones[j].Rotation,
                                out addTranslation, out addRotation);
                            bones[j].Translation = bonesLast[j].Translation + addTranslation;
                            bones[j].Rotation = bonesLast[j].Rotation * addRotation;
                        }
                        else
                        {
                            // unaffected, copy last result (deep copy because we allocate from pool and bone is currenlty class)
                            bones[j].Translation = bonesLast[j].Translation;
                            bones[j].Rotation = bonesLast[j].Rotation;
                        }
                    }
                }
                ResultBonesPool.Free(animationUpdateDataLast.BonesResult);
            }

        }
        // 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
        }
        // Compute current parameter value.
        private float ComputeParamValue(ref MyAnimationUpdateData data)
        {
            // expecting that ChildMappings.Count > 0, see calling function
            float minVal = ChildMappings[0].ParamValueBinding;
            float maxVal = ChildMappings[ChildMappings.Count - 1].ParamValueBinding;
            float smallestDifferenceToNotice = 0.001f * (maxVal - minVal);

            float currParamValue;

            data.Controller.Variables.GetValue(ParameterName, out currParamValue);
            if (m_lastKnownParamValue.HasValue && data.Controller.FrameCounter - m_lastKnownFrameCounter <= 1)
            {
                if (Circular)
                {
                    // find the closest, wrap around if closer
                    float lastClosestValue = m_lastKnownParamValue.Value;
                    // (diff between current and last)^2
                    float diffNoWrap2 = currParamValue - m_lastKnownParamValue.Value;
                    diffNoWrap2 = diffNoWrap2 * diffNoWrap2;
                    float minDiff2 = diffNoWrap2;
                    // (diff between current and last + range)^2
                    float diffWrapRight = currParamValue - (m_lastKnownParamValue.Value + maxVal - minVal);
                    if (diffWrapRight * diffWrapRight < diffNoWrap2)
                    {
                        lastClosestValue = m_lastKnownParamValue.Value + maxVal - minVal;
                        minDiff2         = diffWrapRight * diffWrapRight;
                    }
                    // (diff between current and last - range)^2
                    float diffWrapLeft = currParamValue - (m_lastKnownParamValue.Value - maxVal + minVal);
                    if (diffWrapLeft * diffWrapLeft < minDiff2)
                    {
                        lastClosestValue = m_lastKnownParamValue.Value - maxVal + minVal;
                        minDiff2         = diffWrapLeft * diffWrapLeft;
                    }

                    // interpolate between cuttent and last
                    float nonwrappedValue = minDiff2 <= MaxChange *MaxChange?MathHelper.Lerp(lastClosestValue, currParamValue, Sensitivity) : currParamValue;

                    // wrap the value in the interval
                    while (nonwrappedValue < minVal)
                    {
                        nonwrappedValue += maxVal - minVal;
                    }
                    while (nonwrappedValue > maxVal)
                    {
                        nonwrappedValue -= maxVal - minVal;
                    }

                    // remember the value
                    if ((m_lastKnownParamValue.Value - nonwrappedValue) * (m_lastKnownParamValue.Value - nonwrappedValue)
                        > smallestDifferenceToNotice * smallestDifferenceToNotice)
                    {
                        m_lastKnownParamValue = nonwrappedValue;
                    }
                }
                else
                {
                    // interpolate between current and last
                    float diff         = (currParamValue - m_lastKnownParamValue.Value);
                    float currentValue = diff * diff <= MaxChange *MaxChange?MathHelper.Lerp(m_lastKnownParamValue.Value, currParamValue, Sensitivity) : currParamValue;

                    // remember the value
                    if ((m_lastKnownParamValue.Value - currentValue) * (m_lastKnownParamValue.Value - currentValue)
                        > smallestDifferenceToNotice * smallestDifferenceToNotice)
                    {
                        m_lastKnownParamValue = currentValue;
                    }
                }
            }
            else
            {
                m_lastKnownParamValue = currParamValue;
            }
            return(m_lastKnownParamValue.Value);
        }
        // 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 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 state machine. That means:
        //   Change state if transition conditions are fulfilled.
        //   Update skeleton.
        public void Update(ref MyAnimationUpdateData data)
        {
            if (data.CharacterBones == null)
                return; // safety
            CurrentUpdateData = data;                         // local copy
            
            CurrentUpdateData.VisitedTreeNodesCounter = 0;
            CurrentUpdateData.VisitedTreeNodesPath = m_lastVisitedTreeNodesPath;
            CurrentUpdateData.VisitedTreeNodesPath[0] = 0;

            if (BoneMask == null)                             // rebuild bone mask array if not done yet
                RebuildBoneMask();
            data.LayerBoneMask = CurrentUpdateData.LayerBoneMask = BoneMask;       // pass bone mask array to all subnodes and result
            Debug.Assert(data.LayerBoneMask != null);

            // setting animation finished flag
            MyAnimationStateMachineNode currentAnimationNode = CurrentNode as MyAnimationStateMachineNode;
            if (currentAnimationNode != null && currentAnimationNode.RootAnimationNode != null)
            {
                float finishedPercent = currentAnimationNode.RootAnimationNode.GetLocalTimeNormalized();
                data.Controller.Variables.SetValue(MyAnimationVariableStorageHints.StrIdAnimationFinished, finishedPercent);
            }
            else
            {
                data.Controller.Variables.SetValue(MyAnimationVariableStorageHints.StrIdAnimationFinished, 0);
            }

            base.Update();

            int[] swapVisitedTreeNodesPath = VisitedTreeNodesPath;
            VisitedTreeNodesPath = m_lastVisitedTreeNodesPath;
            m_lastVisitedTreeNodesPath = swapVisitedTreeNodesPath;
            CurrentUpdateData.VisitedTreeNodesPath = null;     // disconnect our array

            // blended transitions
            //    - from most recent to oldest
            //    - please preserve order of blending for three or more states
            //      when CurrentState changes
            float weightMultiplier = 1.0f;
            for (int i = 0; i < m_stateTransitionBlending.Count; i++)
            {
                MyStateTransitionBlending stateTransitionBlending = m_stateTransitionBlending[i];
                float localWeight = (float)(stateTransitionBlending.TimeLeftInSeconds * stateTransitionBlending.InvTotalTime); // 1 to 0 over time
                weightMultiplier *= localWeight;

                // update nodes that we are just leaving
                if (weightMultiplier > 0)
                {
                    var lastResult = CurrentUpdateData.BonesResult;
                    CurrentUpdateData.BonesResult = null;
                    stateTransitionBlending.SourceState.OnUpdate(this);
                    if (lastResult != null && CurrentUpdateData.BonesResult != null)
                    {
                        for (int j = 0; j < lastResult.Count; j++)
                            if (data.LayerBoneMask[j])
                            {
                                // these nodes lose their weight, it goes from 1 to 0
                                // we need to blend them to last result (current node or another node that we are leaving)
                                float w = ComputeEaseInEaseOut(MathHelper.Clamp(weightMultiplier, 0, 1));
                                CurrentUpdateData.BonesResult[j].Rotation = Quaternion.Slerp(lastResult[j].Rotation,
                                    CurrentUpdateData.BonesResult[j].Rotation, w);
                                CurrentUpdateData.BonesResult[j].Translation = Vector3.Lerp(lastResult[j].Translation,
                                    CurrentUpdateData.BonesResult[j].Translation, w);
                            }
                        // give back last result (free list of bones), we dont need it anymore
                        data.Controller.ResultBonesPool.Free(lastResult);
                    }
                }
                // update, decrease remaining time
                stateTransitionBlending.TimeLeftInSeconds -= data.DeltaTimeInSeconds;
                m_stateTransitionBlending[i] = stateTransitionBlending;
                if (stateTransitionBlending.TimeLeftInSeconds <= 0 || weightMultiplier <= 0)
                {
                    // skip older blended states and mark them for deletion, because their (global) weight is now zero 
                    for (int j = i + 1; j < m_stateTransitionBlending.Count; j++)
                    {
                        var temp = m_stateTransitionBlending[j];
                        temp.TimeLeftInSeconds = 0;  // 
                        m_stateTransitionBlending[j] = temp;
                    }
                    break; 
                }
            }
            m_stateTransitionBlending.RemoveAll(s => s.TimeLeftInSeconds <= 0.0);

            data.BonesResult = CurrentUpdateData.BonesResult; // local copy contains resulting list of bones
        }
        /// <summary>
        /// Synchronize time with defined layer. Returns false if the time is not synchronized.
        /// </summary>
        private bool ProcessLayerTimeSync(ref MyAnimationUpdateData data)
        {
            if (m_synchronizeWithLayerRef == null)
            {
                if (m_synchronizeWithLayerName == null)
                {
                    return false;
                }
                
                m_synchronizeWithLayerRef = data.Controller.GetLayerByName(m_synchronizeWithLayerName);
                if (m_synchronizeWithLayerRef == null)
                    return false;
            }
            var nodeFromSyncLayer = m_synchronizeWithLayerRef.CurrentNode as MyAnimationStateMachineNode;
            if (nodeFromSyncLayer == null || nodeFromSyncLayer.RootAnimationNode == null)
                return false;

            SetLocalTimeNormalized(nodeFromSyncLayer.RootAnimationNode.GetLocalTimeNormalized());
            return true;
        }
        // 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
        }
        // Compute current parameter value.
        private float ComputeParamValue(ref MyAnimationUpdateData data)
        {
            // expecting that ChildMappings.Count > 0, see calling function
            float minVal = ChildMappings[0].ParamValueBinding;
            float maxVal = ChildMappings[ChildMappings.Count - 1].ParamValueBinding;
            float smallestDifferenceToNotice = 0.001f * (maxVal - minVal);

            float currParamValue;
            data.Controller.Variables.GetValue(ParameterName, out currParamValue);
            if (m_lastKnownParamValue.HasValue && data.Controller.FrameCounter - m_lastKnownFrameCounter <= 1)
            {
                if (Circular)
                {
                    // find the closest, wrap around if closer
                    float lastClosestValue = m_lastKnownParamValue.Value;
                    // (diff between current and last)^2
                    float diffNoWrap2 = currParamValue - m_lastKnownParamValue.Value;
                    diffNoWrap2 = diffNoWrap2 * diffNoWrap2;
                    float minDiff2 = diffNoWrap2;
                    // (diff between current and last + range)^2
                    float diffWrapRight = currParamValue - (m_lastKnownParamValue.Value + maxVal - minVal);
                    if (diffWrapRight * diffWrapRight < diffNoWrap2)
                    {
                        lastClosestValue = m_lastKnownParamValue.Value + maxVal - minVal;
                        minDiff2 = diffWrapRight * diffWrapRight;
                    }
                    // (diff between current and last - range)^2
                    float diffWrapLeft = currParamValue - (m_lastKnownParamValue.Value - maxVal + minVal);
                    if (diffWrapLeft * diffWrapLeft < minDiff2)
                    {
                        lastClosestValue = m_lastKnownParamValue.Value - maxVal + minVal;
                        minDiff2 = diffWrapLeft * diffWrapLeft;
                    }

                    // interpolate between cuttent and last
                    float nonwrappedValue = minDiff2 <= MaxChange * MaxChange ? MathHelper.Lerp(lastClosestValue, currParamValue, Sensitivity) : currParamValue;
                    // wrap the value in the interval
                    while (nonwrappedValue < minVal)
                        nonwrappedValue += maxVal - minVal;
                    while (nonwrappedValue > maxVal)
                        nonwrappedValue -= maxVal - minVal;

                    // remember the value
                    if ((m_lastKnownParamValue.Value - nonwrappedValue) * (m_lastKnownParamValue.Value - nonwrappedValue) 
                        > smallestDifferenceToNotice * smallestDifferenceToNotice)
                    {
                        m_lastKnownParamValue = nonwrappedValue;
                    }
                }
                else
                {
                    // interpolate between current and last
                    float diff = (currParamValue - m_lastKnownParamValue.Value);
                    float currentValue = diff * diff <= MaxChange * MaxChange ? MathHelper.Lerp(m_lastKnownParamValue.Value, currParamValue, Sensitivity) : currParamValue;
                    // remember the value
                    if ((m_lastKnownParamValue.Value - currentValue) * (m_lastKnownParamValue.Value - currentValue)
                        > smallestDifferenceToNotice * smallestDifferenceToNotice)
                    {
                        m_lastKnownParamValue = currentValue;
                    }
                }
            }
            else
            {
                m_lastKnownParamValue = currParamValue;
            }
            return m_lastKnownParamValue.Value;
        }
Exemple #12
0
        // Update state machine. That means:
        //   Change state if transition conditions are fulfilled.
        //   Update skeleton.
        public void Update(ref MyAnimationUpdateData data)
        {
            if (data.CharacterBones == null)
            {
                return;               // safety
            }
            CurrentUpdateData = data; // local copy

            CurrentUpdateData.VisitedTreeNodesCounter = 0;
            CurrentUpdateData.VisitedTreeNodesPath    = m_lastVisitedTreeNodesPath;
            CurrentUpdateData.VisitedTreeNodesPath[0] = 0;

            if (BoneMask == null)                             // rebuild bone mask array if not done yet
            {
                RebuildBoneMask();
            }
            data.LayerBoneMask = CurrentUpdateData.LayerBoneMask = BoneMask;       // pass bone mask array to all subnodes and result
            Debug.Assert(data.LayerBoneMask != null);

            // setting animation finished flag
            MyAnimationStateMachineNode currentAnimationNode = CurrentNode as MyAnimationStateMachineNode;

            if (currentAnimationNode != null && currentAnimationNode.RootAnimationNode != null)
            {
                float finishedPercent = currentAnimationNode.RootAnimationNode.GetLocalTimeNormalized();
                data.Controller.Variables.SetValue(MyAnimationVariableStorageHints.StrIdAnimationFinished, finishedPercent);
            }
            else
            {
                data.Controller.Variables.SetValue(MyAnimationVariableStorageHints.StrIdAnimationFinished, 0);
            }

            base.Update();

            int[] swapVisitedTreeNodesPath = VisitedTreeNodesPath;
            VisitedTreeNodesPath                   = m_lastVisitedTreeNodesPath;
            m_lastVisitedTreeNodesPath             = swapVisitedTreeNodesPath;
            CurrentUpdateData.VisitedTreeNodesPath = null;     // disconnect our array

            // blended transitions
            //    - from most recent to oldest
            //    - please preserve order of blending for three or more states
            //      when CurrentState changes
            float weightMultiplier = 1.0f;

            for (int i = 0; i < m_stateTransitionBlending.Count; i++)
            {
                MyStateTransitionBlending stateTransitionBlending = m_stateTransitionBlending[i];
                float localWeight = (float)(stateTransitionBlending.TimeLeftInSeconds * stateTransitionBlending.InvTotalTime); // 1 to 0 over time
                weightMultiplier *= localWeight;

                // update nodes that we are just leaving
                if (weightMultiplier > 0)
                {
                    var lastResult = CurrentUpdateData.BonesResult;
                    CurrentUpdateData.BonesResult = null;
                    stateTransitionBlending.SourceState.OnUpdate(this);
                    if (lastResult != null && CurrentUpdateData.BonesResult != null)
                    {
                        for (int j = 0; j < lastResult.Count; j++)
                        {
                            if (data.LayerBoneMask[j])
                            {
                                // these nodes lose their weight, it goes from 1 to 0
                                // we need to blend them to last result (current node or another node that we are leaving)
                                float w = ComputeEaseInEaseOut(MathHelper.Clamp(weightMultiplier, 0, 1));
                                CurrentUpdateData.BonesResult[j].Rotation = Quaternion.Slerp(lastResult[j].Rotation,
                                                                                             CurrentUpdateData.BonesResult[j].Rotation, w);
                                CurrentUpdateData.BonesResult[j].Translation = Vector3.Lerp(lastResult[j].Translation,
                                                                                            CurrentUpdateData.BonesResult[j].Translation, w);
                            }
                        }
                        // give back last result (free list of bones), we dont need it anymore
                        data.Controller.ResultBonesPool.Free(lastResult);
                    }
                }
                // update, decrease remaining time
                stateTransitionBlending.TimeLeftInSeconds -= data.DeltaTimeInSeconds;
                m_stateTransitionBlending[i] = stateTransitionBlending;
                if (stateTransitionBlending.TimeLeftInSeconds <= 0 || weightMultiplier <= 0)
                {
                    // skip older blended states and mark them for deletion, because their (global) weight is now zero
                    for (int j = i + 1; j < m_stateTransitionBlending.Count; j++)
                    {
                        var temp = m_stateTransitionBlending[j];
                        temp.TimeLeftInSeconds       = 0; //
                        m_stateTransitionBlending[j] = temp;
                    }
                    break;
                }
            }
            m_stateTransitionBlending.RemoveAll(s => s.TimeLeftInSeconds <= 0.0);

            data.BonesResult = CurrentUpdateData.BonesResult; // local copy contains resulting list of bones
        }
Exemple #13
0
        /// <summary>
        /// Update this animation controller.
        /// </summary>
        /// <param name="animationUpdateData">See commentary in MyAnimationUpdateData</param>
        public void Update(ref MyAnimationUpdateData animationUpdateData)
        {
            FrameCounter++;
            if (animationUpdateData.CharacterBones == null || !ResultBonesPool.IsValid())
            {
                return; // safety
            }
            if (animationUpdateData.Controller == null)
            {
                animationUpdateData.Controller = this;
            }
            ResultBonesPool.FreeAll(); // free all previous bone allocations, return them to pool
                                       // (this is a safe place to do that, nobody should use them by this time)

            Variables.SetValue(MyAnimationVariableStorageHints.StrIdRandomStable, MyRandom.Instance.NextFloat());

            // Set the first layer.
            if (m_layers.Count > 0)
            {
                ResultBonesPool.SetDefaultPose(null);
                m_layers[0].Update(ref animationUpdateData);
            }
            // Other layers can replace some transforms or add them
            for (int i = 1; i < m_layers.Count; i++)
            {
                var currentLayer = m_layers[i];
                MyAnimationUpdateData animationUpdateDataLast = animationUpdateData;
                animationUpdateData.LayerBoneMask = null;
                animationUpdateData.BonesResult   = null;
                ResultBonesPool.SetDefaultPose(currentLayer.Mode == MyAnimationStateMachine.MyBlendingMode.Replace
                    ? animationUpdateDataLast.BonesResult
                    : null);
                m_layers[i].Update(ref animationUpdateData);
                if (animationUpdateData.BonesResult == null ||
                    m_layers[i].CurrentNode == null ||  // optimization
                    ((MyAnimationStateMachineNode)m_layers[i].CurrentNode).RootAnimationNode == null || // optimization
                    ((MyAnimationStateMachineNode)m_layers[i].CurrentNode).RootAnimationNode is MyAnimationTreeNodeDummy) // optimization
                {
                    animationUpdateData = animationUpdateDataLast;                                                        // restore backup
                    continue;
                }

                int boneCount     = animationUpdateData.BonesResult.Count;
                var bonesLast     = animationUpdateDataLast.BonesResult;
                var bones         = animationUpdateData.BonesResult;
                var bonesBindPose = animationUpdateDataLast.CharacterBones;

                if (currentLayer.Mode == MyAnimationStateMachine.MyBlendingMode.Replace)
                {
                    for (int j = 0; j < boneCount; j++)
                    {
                        if (animationUpdateData.LayerBoneMask[j] == false)
                        {
                            bones[j].Translation = bonesLast[j].Translation;
                            bones[j].Rotation    = bonesLast[j].Rotation;
                        }
                    }
                }
                else if (currentLayer.Mode == MyAnimationStateMachine.MyBlendingMode.Add)
                {
                    for (int j = 0; j < boneCount; j++)
                    {
                        if (animationUpdateData.LayerBoneMask[j])
                        {
                            // add result to current
                            Vector3    addTranslation;
                            Quaternion addRotation;
                            bonesBindPose[j].GetCompleteTransform(ref bones[j].Translation, ref bones[j].Rotation,
                                                                  out addTranslation, out addRotation);
                            bones[j].Translation = bonesLast[j].Translation + addTranslation;
                            bones[j].Rotation    = bonesLast[j].Rotation * addRotation;
                        }
                        else
                        {
                            // unaffected, copy last result (deep copy because we allocate from pool and bone is currenlty class)
                            bones[j].Translation = bonesLast[j].Translation;
                            bones[j].Rotation    = bonesLast[j].Rotation;
                        }
                    }
                }
                ResultBonesPool.Free(animationUpdateDataLast.BonesResult);
            }
        }
        // Update animation state (position and orientation of bones).
        // Called from MySessionComponentAnimationSystem.
        public void Update()
        {
            using (m_componentValidLock.AcquireSharedUsing())
            {
                if (!m_componentValid)
                    return;

                VRageRender.Animations.MyAnimationUpdateData updateData = new VRageRender.Animations.MyAnimationUpdateData();
                updateData.DeltaTimeInSeconds = VRage.Game.MyEngineConstants.UPDATE_STEP_SIZE_IN_SECONDS;
                updateData.CharacterBones = m_characterBones;
                updateData.Controller = null;  // will be set inside m_controller.Update automatically if null
                updateData.BonesResult = null; // will be set inside m_controller.Update (result!)

                m_controller.Update(ref updateData);
                if (updateData.BonesResult != null)
                {
                    for (int i = 0; i < updateData.BonesResult.Count; i++)
                    {
                        CharacterBones[i].SetCompleteTransform(ref updateData.BonesResult[i].Translation,
                            ref updateData.BonesResult[i].Rotation);
                    }
                }
                m_lastBoneResult = updateData.BonesResult;

#if OFFICIAL_BUILD == false
                var helperLast = m_lastFrameActions;
                m_lastFrameActions = m_currentFrameActions;
                m_currentFrameActions = helperLast;
                if (m_currentFrameActions != null)
                    m_currentFrameActions.Clear();
#endif
            }
        }