/// <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 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; }
// 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> /// 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 } }