using System.Collections.Generic; using System.Linq; using UnityEngine; namespace FIMSpace.AnimationTools { #region Leg Animation Clip Motion Analysis Data Class [System.Serializable] public class LegAnimationClipMotion { public bool analyzed = false; public AnimationClip targetClip; public LegAnimationClipMotion( AnimationClip clip ) { analyzed = false; targetClip = clip; } public List sampledData; /// Lowest local position values for each axis in this Animation Clip public Vector3 LowestFootCoords; /// Highest local position values for each axis in this Animation Clip public Vector3 HighestFootCoords; /// Difference values between highest and lowest local position in this Animation Clip public Vector3 FootCoordsDiffs; /// Position in which foot starts touching ground in this animation clip public Vector3 startStepFootLocal; public int startStepFootLocalIndex; public float startStepFootProgress; /// Position in which foot ends touching ground in this animation clip public Vector3 endStepFootLocal; public int endStepFootLocalIndex; public float endStepFootProgress; /// Approximate velocity of animation basing on foot movement when foot is near to the ground public Vector3 approximateFootPushDirection; /// Just 0,0,-1 or 1,0,0 normalized vector value public Vector3 approximateFootPushDirectionDominant; /// Approximate foot left-right position in animator local space during touching ground public float approximateFootLocalXPosDuringPush; public float approximateFootLocalZPosDuringPush; /// Coordinates calculated from T-Pose public Vector3 _footForward = Vector3.forward; public Vector3 _footToToes = Vector3.forward; public Vector3 _footToToesForw = Vector3.forward; public Vector3 _footLocalToGround = Vector3.zero; public float _latestToesTesh = 1f; public float _latestHeelTesh = 1f; public float _latestHoldOnGround = 0f; public float _latestFloorOffset = 0f; public EAnalyzeMode _latestAnalyzeMode = EAnalyzeMode.HeelFeetTight; public float _groundToFootHeight = 0f; public Quaternion _initFootRot; public Quaternion _initFootLocRot; public Quaternion _footRotMapping; public AnimationCurve GroundingCurve; public enum EAnalyzeMode { HeelFeetTight, HeelFeetWide } /// When calling analyze character must be set in T-Pose! public List AnalyzeClip( Transform rootTr, Transform hips, Transform upperLeg, Transform knee, Transform foot, Transform optionalToes, Transform animator, int samples = 20, float heelTreshold = 1f, float toesTreshold = 1f, float holdOnGround = 0f, bool displayEditorProgressBar = false, bool removeRootMotion = false, float floorOffset = 0f, EAnalyzeMode mode = EAnalyzeMode.HeelFeetTight, int cutFirstFrames = 0, int cutLastFrames = 0 ) { analyzed = false; Matrix4x4 rootMx = Matrix4x4.TRS( animator.position, animator.rotation, animator.lossyScale ); Matrix4x4 rootMxRev = rootMx.inverse; #region Analyze Helper Definitions float toesGroundedTresh = toesTreshold; float heelGroundedTresh = heelTreshold; _latestToesTesh = toesTreshold; _latestHeelTesh = heelTreshold; _latestHoldOnGround = holdOnGround; _latestAnalyzeMode = mode; _latestFloorOffset = floorOffset; if( cutFirstFrames < 0 ) cutFirstFrames = 0; if( cutLastFrames < 0 ) cutLastFrames = 0; #endregion sampledData = new List(); StorePoseBackup( rootTr ); _initFootRot = foot.rotation; _initFootLocRot = foot.localRotation; _groundToFootHeight = rootTr.InverseTransformPoint( foot.position ).y; // Referencing from initial character pose //// Referencing from initial animation clip frame //targetClip.SampleAnimation(animator.gameObject, 0f); float stepOffset = (float)cutFirstFrames / samples; samples -= ( cutFirstFrames + cutLastFrames ); float step = 1f / (float)samples; if( displayEditorProgressBar ) ProgressBar( "Preparing foot analysis...", 0f ); #region Getting Root Motion Bone Transform rootMotionBone = null; SkinnedMeshRenderer[] skin = animator.GetComponentsInChildren(); if( skin != null ) if( skin.Length > 0 ) { SkinnedMeshRenderer mostBones = skin.OrderBy( s => s.bones.Length ).First(); if( mostBones != null ) rootMotionBone = mostBones.rootBone; } if( rootMotionBone == null ) rootMotionBone = hips; #endregion #region Calculating helper directions Vector3 localFootToGround, ankleForward, ankleToToes, ankleToToesForward, ankleToToesNorm; localFootToGround = new Vector3( foot.position.x, rootTr.position.y, foot.position.z ); localFootToGround = foot.InverseTransformPoint( localFootToGround ); ankleForward = foot.InverseTransformPoint( foot.position + rootTr.forward ).normalized; if( optionalToes ) ankleToToes = foot.InverseTransformPoint( optionalToes.position ); else { ankleToToes = ankleForward * ( Vector3.Distance( foot.position, knee.position ) + Vector3.Distance( upperLeg.position, knee.position ) ) * 0.325f; } if( optionalToes ) { Vector3 mapped = optionalToes.position; mapped.y = foot.position.y; ankleToToesForward = foot.InverseTransformPoint( mapped ); } else { ankleToToesForward = ankleToToes; } ankleToToesNorm = ankleToToes.normalized; _footForward = ankleForward; _footToToes = ankleToToes; _footToToesForw = ankleToToesForward; _footLocalToGround = localFootToGround; _footRotMapping = Quaternion.FromToRotation( foot.InverseTransformDirection( rootTr.right ), Vector3.right ); _footRotMapping *= Quaternion.FromToRotation( foot.InverseTransformDirection( rootTr.up ), Vector3.up ); #endregion #region Sampling Positions Vector3 startRootPosition = rootTr.InverseTransformPoint( rootMotionBone.position ); GroundingCurve = new AnimationCurve(); for( int i = 0; i < samples; i++ ) { if( displayEditorProgressBar ) ProgressBar( "Sampling leg animation data " + i + " / " + samples, (float)i / (float)samples ); MotionSample s = new MotionSample(); // rootMotionBone Root Motion Cancelling ----------------------------------- targetClip.SampleAnimation( animator.gameObject, stepOffset + step * i * targetClip.length ); if( removeRootMotion ) { rootMotionBone.localPosition = Vector3.zero; Vector3 hipsLocal = animator.InverseTransformPoint( hips.position ); hipsLocal.z = 0f; hips.position = animator.TransformPoint( hipsLocal ); } s.sampledAnkleRoot = rootMxRev.MultiplyPoint( foot.position ); Vector3 preR = rootMotionBone.position; Vector3 refPosition = rootTr.InverseTransformPoint( rootMotionBone.position ); rootMotionBone.position = rootTr.TransformPoint( refPosition.x, refPosition.y, startRootPosition.z ); s.sampledRootLocal = rootTr.InverseTransformPoint( rootMotionBone.position ); s.sampledFootInRMLocal = rootMotionBone.InverseTransformPoint( rootMotionBone.position ); s.sampledFootInAnimLocal = animator.InverseTransformPoint( rootMotionBone.position ); // Foot Center grounding position ----------------------------------- Vector3 sampledFoot = foot.position; s.sampledAnkleLocal = rootTr.InverseTransformPoint( sampledFoot ); // Foot ankle sampledFoot += foot.TransformDirection( localFootToGround ); s.sampledFootLocal = rootTr.InverseTransformPoint( sampledFoot ); // Foot on ground // Toes ----------------------------------- Vector3 sampledToes = GetSamplingToesPoint( foot, toesTreshold ); // Heel ----------------------------------- Vector3 sampledHeel = GetSamplingHeelPoint( foot, heelTreshold ); if( mode == EAnalyzeMode.HeelFeetTight ) { s.sampledToesLocal = rootTr.InverseTransformPoint( sampledToes ); s.sampledHeelLocal = rootTr.InverseTransformPoint( sampledHeel ); } else { sampledToes = foot.position; sampledToes += foot.TransformDirection( ankleToToes ); // Ankle position offsetted to ground position sampledToes += foot.TransformDirection( ankleToToesForward ) * localFootToGround.magnitude * 4f; // Ankle position slightly forwarded s.sampledToesLocal = rootTr.InverseTransformPoint( sampledToes ); sampledHeel = foot.position; sampledHeel += foot.TransformDirection( localFootToGround ); // Ankle position offsetted to ground position sampledHeel -= foot.TransformDirection( ankleForward ) * localFootToGround.magnitude * .8f; // Ankle position slightly forwarded s.sampledHeelLocal = rootTr.InverseTransformPoint( sampledHeel ); } // Knee and upper leg --------- s.sampledKneeLocal = rootTr.InverseTransformPoint( knee.position ); s.sampledUpperLegLocal = rootTr.InverseTransformPoint( upperLeg.position ); sampledData.Add( s ); } if( displayEditorProgressBar ) ProgressBar( "Restoring character pose", 1f ); #region Restoring object pose -------------------------------------------- AnimationClip getDefaultClip = targetClip; string[] defaultKeywords = new string[] { "idle", "stop", "none" }; Animator anim = animator.GetComponent(); if( anim ) { if( anim.runtimeAnimatorController ) { for( int i = 0; i < anim.runtimeAnimatorController.animationClips.Length; i++ ) for( int k = 0; k < defaultKeywords.Length; k++ ) if( anim.runtimeAnimatorController.animationClips[i].name.ToLower().Contains( defaultKeywords[k] ) ) { getDefaultClip = anim.runtimeAnimatorController.animationClips[i]; break; } } else { UnityEngine.Debug.Log( "[Error] No Animator Controller in " + anim.name ); } } getDefaultClip.SampleAnimation( animator.gameObject, 0f ); #endregion #endregion if( displayEditorProgressBar ) ProgressBar( "Checking collected data", 0f ); // Lowest / Highest coords --------------------------------------- LowestFootCoords = new Vector3( float.MaxValue, float.MaxValue, float.MaxValue ); HighestFootCoords = new Vector3( float.MinValue, float.MinValue, float.MinValue ); for( int i = 0; i < samples; ++i ) { if( sampledData[i].sampledFootLocal.x < LowestFootCoords.x ) LowestFootCoords.x = sampledData[i].sampledFootLocal.x; if( sampledData[i].sampledFootLocal.y < LowestFootCoords.y ) LowestFootCoords.y = sampledData[i].sampledFootLocal.y; if( sampledData[i].sampledFootLocal.z < LowestFootCoords.z ) LowestFootCoords.z = sampledData[i].sampledFootLocal.z; if( sampledData[i].sampledFootLocal.x > HighestFootCoords.x ) HighestFootCoords.x = sampledData[i].sampledFootLocal.x; if( sampledData[i].sampledFootLocal.y > HighestFootCoords.y ) HighestFootCoords.y = sampledData[i].sampledFootLocal.y; if( sampledData[i].sampledFootLocal.z > HighestFootCoords.z ) HighestFootCoords.z = sampledData[i].sampledFootLocal.z; } FootCoordsDiffs = new Vector3( LowestFootCoords.x - HighestFootCoords.x, LowestFootCoords.y - HighestFootCoords.y, LowestFootCoords.z - HighestFootCoords.z ); // Analyzing motion with sampled data float refScale = GetRefScale( knee, foot ); float heightTreshold = GetHeightTresholdScale( knee, foot, refScale ); bool? lastGrounded = null; float holding = 0f; float ungroundFor = 0f; float stepLen = (float)1 / (float)samples; approximateFootLocalXPosDuringPush = sampledData[0].sampledFootLocal.x; approximateFootLocalZPosDuringPush = sampledData[0].sampledFootLocal.z; bool wasAppFootXPushGrnd = false; bool wasAppFootZPushGrnd = false; // Grounded set to true when foot-toes bottom or heel on ground (with 100% toes grounding was too long) // To false when toes and heel over ground position for( int i = 0; i < samples; ++i ) { if( displayEditorProgressBar ) ProgressBar( "Sampling collected data " + i + " / " + samples, (float)i / (float)samples ); float cycle = (float)i / (float)samples; MotionSample s = sampledData[i]; s.grounded = false; if( mode == EAnalyzeMode.HeelFeetTight ) { // Heel foot position y check if( s.sampledHeelLocal.y < heightTreshold * heelGroundedTresh + floorOffset ) s.grounded = true; // Middle foot position y check //float footMidToes = Mathf.LerpUnclamped(s.sampledFootLocal.y, s.sampledToesLocal.y, 0.6f); if( s.sampledToesLocal.y < heightTreshold * toesGroundedTresh + floorOffset ) s.grounded = true; } else { if( s.sampledHeelLocal.y < heightTreshold * heelGroundedTresh + floorOffset || s.sampledFootLocal.y < heightTreshold * toesGroundedTresh + floorOffset ) s.grounded = true; } // Hold feature if( s.grounded && lastGrounded == true ) { holding += stepLen; ungroundFor = 0f; } else if( holding > 0f ) { if( holdOnGround > 0f ) if( holding < holdOnGround ) { holding += stepLen; s.grounded = true; } } else { ungroundFor += stepLen; if( ungroundFor > stepLen * 3 ) if( holding > holdOnGround ) holding = 0f; } // Detected grounding if( lastGrounded != true && s.grounded == true ) { startStepFootLocal = s.sampledFootLocal; startStepFootLocalIndex = i; startStepFootProgress = (float)i / (float)( samples ); } else // Detected unground if( lastGrounded == true && s.grounded == false ) { endStepFootLocal = s.sampledFootLocal; endStepFootLocalIndex = i; endStepFootProgress = (float)i / (float)( samples ); } lastGrounded = s.grounded; if( s.grounded ) { float preCycle = cycle - 2f / (float)samples; if( preCycle < 0f ) preCycle += 1f; Vector3 diff = GetFootLocalPosition( cycle ) - GetFootLocalPosition( preCycle ); diff.y = 0f; approximateFootPushDirection += diff; #region Approximate Local Foot X when grounded if( wasAppFootXPushGrnd == false ) { wasAppFootXPushGrnd = true; approximateFootLocalXPosDuringPush = s.sampledFootLocal.x; } else { approximateFootLocalXPosDuringPush = Mathf.Lerp( approximateFootLocalXPosDuringPush, s.sampledFootLocal.x, 0.5f ); } #endregion #region Approximate Local Foot Z when grounded if( wasAppFootZPushGrnd == false ) { wasAppFootZPushGrnd = true; approximateFootLocalZPosDuringPush = s.sampledFootLocal.z; } else { approximateFootLocalZPosDuringPush = Mathf.Lerp( approximateFootLocalZPosDuringPush, s.sampledFootLocal.z, 0.5f ); } #endregion } approximateFootPushDirection.Normalize(); approximateFootPushDirectionDominant = FIMSpace.FVectorMethods.ChooseDominantAxis( approximateFootPushDirection ); // Checking for leg swinging Vector3 pre = GetFootLocalPosition( cycle - ( step ) ); Vector3 curr = GetFootLocalPosition( cycle ); if( curr.z > pre.z ) s.swingForwards = true; float refSc = refScale; if( refSc < 0.4f ) refSc = 0.4f; float val = Mathf.Clamp( s.sampledFootLocal.y / refSc, 0.2f, 1f ); if( s.grounded ) val = 0f; GroundingCurve.AddKey( cycle, val ); } List optimizedCurve = new List(); optimizedCurve.Add( GroundingCurve.keys[0] ); for( int i = 1; i < GroundingCurve.keys.Length - 1; i++ ) { if( GroundingCurve.keys[i].value == 0f && GroundingCurve.keys[i + 1].value > 0f ) { optimizedCurve.Add( GroundingCurve.keys[i] ); continue; } if( GroundingCurve.keys[i - 1].value > 0f && GroundingCurve.keys[i].value == 0f ) { optimizedCurve.Add( GroundingCurve.keys[i] ); continue; } if( GroundingCurve.keys[i - 1].value != GroundingCurve.keys[i].value ) optimizedCurve.Add( GroundingCurve.keys[i] ); } optimizedCurve.Add( GroundingCurve.keys[GroundingCurve.keys.Length - 1] ); GroundingCurve = new AnimationCurve( optimizedCurve.ToArray() ); //for( int i = 0; i < GroundingCurve.keys.Length; i++ ) //{ // //GroundingCurve.SmoothTangents(i, 0.5f); // var key = GroundingCurve.keys[i]; // key.weightedMode = WeightedMode.Both; // if( i < GroundingCurve.keys.Length - 1 ) // { // var nextKey = GroundingCurve.keys[i + 1]; // var delta = new Vector2( key.time, key.value ) - new Vector2( nextKey.time, nextKey.value ); // key.inTangent = delta.y / delta.x; // } // if( i > 0 ) // { // var preKey = GroundingCurve.keys[i - 1]; // var delta = new Vector2( preKey.time, preKey.value ) - new Vector2( key.time, key.value ); // key.outTangent = delta.y / delta.x; // } // GroundingCurve.MoveKey( i, key ); //} GroundingCurve = AnimationGenerateUtils.ReduceKeyframes( GroundingCurve, 0.1f ); #if UNITY_EDITOR for( int k = 0; k < GroundingCurve.keys.Length; k++ ) { UnityEditor.AnimationUtility.SetKeyLeftTangentMode( GroundingCurve, k, UnityEditor.AnimationUtility.TangentMode.Linear ); UnityEditor.AnimationUtility.SetKeyRightTangentMode( GroundingCurve, k, UnityEditor.AnimationUtility.TangentMode.Linear ); } #endif // Prediction when leg is going to step down --------------------------------------- for( int i = 0; i < samples; ++i ) { if( displayEditorProgressBar ) ProgressBar( "Defining Additional Data " + i + " / " + samples, (float)i / (float)samples ); MotionSample s = sampledData[i]; if( s.grounded ) continue; if( s.sampledFootLocal.z < 0 ) // if foot .z is behind origin then we set prediction state only when foot swings forward { if( s.swingForwards ) { if( s.sampledFootLocal.z > LowestFootCoords.z / 2f ) s.predictState = true; } } else // When foot .z is in front of origin s.predictState = true; } if( displayEditorProgressBar ) ProgressBar( "Finalizing", 1f ); RestorePoseBackup(); if( displayEditorProgressBar ) ProgressBar( "", 1.1f ); analyzed = true; return sampledData; } public float GetRefScale( Transform knee, Transform foot ) { return Vector3.Distance( foot.position, knee.position ) * 0.1f; } public float GetTresholdLength( Transform knee, Transform foot, float treshold, float amplification = 1f ) { float refSc = GetHeightTresholdScale( knee, foot ) + LowestFootCoords.y; return GetAmplified02Range( 1f - treshold, amplification ) * refSc; } public float GetHeightTresholdScale( Transform knee, Transform foot, float? refScale = null ) { float refs; if( refScale == null ) refs = GetRefScale( knee, foot ); else refs = refScale.Value; return refs + LowestFootCoords.y; } #region Requesting positions public Vector3 GetToesFootLocalPosition( float progress, float toesToFoot = 0.5f ) { return Vector3.LerpUnclamped( GetToesLocalPosition( progress ), GetFootLocalPosition( progress ), toesToFoot ); } public Vector3 GetToesLocalPosition( float progress ) { RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledToesLocal, sampledData[i_higherIndex].sampledToesLocal, i_progress ); } public Vector3 GetAnkleLocalPosition( float progress ) { RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledAnkleLocal, sampledData[i_higherIndex].sampledAnkleLocal, i_progress ); } public MotionSample GetSampleInProgress( float progress, bool higher = true ) { RefreshInterpolationIndexes( progress ); if( higher ) return sampledData[i_higherIndex]; else return sampledData[i_lowerIndex]; } public Vector3 GetFootLocalPositionInAnimator( float progress ) { progress = RoundCycle( progress ); RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledFootInAnimLocal, sampledData[i_higherIndex].sampledFootInAnimLocal, i_progress ); } public Vector3 GetFootLocalPositionInRootMotion( float progress ) { progress = RoundCycle( progress ); RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledFootInRMLocal, sampledData[i_higherIndex].sampledFootInRMLocal, i_progress ); } public Vector3 GetFootLocalPosition( float progress ) { progress = RoundCycle( progress ); RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledFootLocal, sampledData[i_higherIndex].sampledFootLocal, i_progress ); } public Vector3 GetHeelLocalPosition( float progress ) { RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledHeelLocal, sampledData[i_higherIndex].sampledHeelLocal, i_progress ); } public Vector3 GetUpperLegLocalPosition( float progress ) { RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledUpperLegLocal, sampledData[i_higherIndex].sampledUpperLegLocal, i_progress ); } public Vector3 GetRootLocalPosition( float progress ) { RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledRootLocal, sampledData[i_higherIndex].sampledRootLocal, i_progress ); } public Vector3 GetKneeLocalPosition( float progress ) { RefreshInterpolationIndexes( progress ); return Vector3.LerpUnclamped( sampledData[i_lowerIndex].sampledKneeLocal, sampledData[i_higherIndex].sampledKneeLocal, i_progress ); } #endregion #region Requesting info public bool GroundedIn( float progress ) { //RefreshInterpolationIndexes(progress); if( sampledData == null ) return false; if( sampledData.Count == 0 ) return false; //if (progress > 1f) progress -= 1f; //if (progress < 0f) progress += 1f; return GroundingCurve.Evaluate( progress ) < 0.05f; //if (i_progress > 0.5f) // return sampledData[i_higherIndex].grounded; //else // return sampledData[i_lowerIndex].grounded; } public bool PredictIn( float progress ) { RefreshInterpolationIndexes( progress ); if( i_progress > 0.5f ) return sampledData[i_higherIndex].predictState; else return sampledData[i_lowerIndex].predictState; } public bool GetSwingingForward( float progress ) { RefreshInterpolationIndexes( progress ); if( i_progress > 0.5f ) return sampledData[i_higherIndex].swingForwards; else return sampledData[i_lowerIndex].swingForwards; } public float GetAmplified02Range( float enterValue, float amplifyAfter1UpTo2 = 5f ) { if( enterValue <= 1f ) return enterValue; float a = -( 1f - enterValue ); return enterValue + a * amplifyAfter1UpTo2; } #region Interpolation Preparations private int i_gameFrame = -1; public int i_lowerIndex; public int i_higherIndex; public float i_progress; public float i_forProgress; /// /// After refreshing use i_lowerIndex and i_higherIndex for interpolation samples /// public void RefreshInterpolationIndexes( float progress ) { if( sampledData == null ) return; if( sampledData.Count == 0 ) return; if( progress == i_forProgress ) if( i_gameFrame == Time.frameCount ) return; // Limiting calculations if not needed to recompute in game frame i_gameFrame = Time.frameCount; i_forProgress = progress; i_progress = RoundCycle( i_forProgress ); i_higherIndex = Mathf.CeilToInt( i_progress * sampledData.Count ); if( i_higherIndex > sampledData.Count - 1 ) { i_lowerIndex = sampledData.Count - 1; i_higherIndex = 0; i_progress = Mathf.InverseLerp( i_lowerIndex, sampledData.Count, i_progress * sampledData.Count ); } else { i_lowerIndex = Mathf.FloorToInt( i_progress * sampledData.Count ); i_progress = Mathf.InverseLerp( i_lowerIndex, i_higherIndex, i_progress * sampledData.Count ); } } #endregion #endregion void ProgressBar( string text, float prog ) { #if UNITY_EDITOR if( string.IsNullOrEmpty( text ) || prog > 1f ) { UnityEditor.EditorUtility.ClearProgressBar(); return; } UnityEditor.EditorUtility.DisplayProgressBar( "Leg Motion Analysis...", text, prog ); #endif } /// /// Rounding cycle to value 0 to 1 /// When progress is 1.5 then returns 0.5 ... When progress is -0.2 then returns 0.8 /// public static float RoundCycle( float cycleProgress ) { return cycleProgress - Mathf.Floor( cycleProgress ); } public LegAnimationClipMotion GetCopy() { return base.MemberwiseClone() as LegAnimationClipMotion; } [System.Serializable] public class MotionSample { #region Sampling positions public Vector3 sampledToesLocal; public Vector3 sampledAnkleRoot; public Vector3 sampledFootLocal; public Vector3 sampledAnkleLocal; public Vector3 sampledHeelLocal; public Vector3 sampledKneeLocal; public Vector3 sampledUpperLegLocal; public Vector3 sampledRootLocal; public Vector3 sampledFootInRMLocal; public Vector3 sampledFootInAnimLocal; #endregion /// Foot touches ground level public bool grounded = false; /// Foot swings forwards but not touching ground public bool predictState = false; /// Swinging forwards instant after pushing back from ground public bool swingForwards = false; /// Swinging forwards with some delay after pushing back from ground //public bool swingForwardsToStep = false; public MotionSample GetCopy() { return base.MemberwiseClone() as MotionSample; } } #region Transforms Pose Restore static void StorePoseBackup( Transform rootTransform ) { poseBackup.Clear(); foreach( Transform t in rootTransform.GetComponentsInChildren() ) { TransformsBackup b = new TransformsBackup(); b.t = t; b.localPos = t.localPosition; b.localRot = t.localRotation; b.localScale = t.localScale; poseBackup.Add( b ); } } static void RestorePoseBackup() { for( int i = poseBackup.Count - 1; i >= 0; i-- ) poseBackup[i].Restore(); poseBackup.Clear(); } public static List poseBackup = new List(); public struct TransformsBackup { public Transform t; public Vector3 localPos; public Quaternion localRot; public Vector3 localScale; public void Restore() { if( t == null ) return; t.localPosition = localPos; t.localRotation = localRot; t.localScale = localScale; } } public Vector3 GetSamplingToesPoint( Transform foot, float tresh ) { Vector3 sampledToes = foot.position; sampledToes += foot.TransformDirection( _footLocalToGround ); // Ankle position offsetted to ground position sampledToes += foot.TransformDirection( _footToToesForw ) * _footLocalToGround.magnitude * ( 4f - Mathf.LerpUnclamped( 0f, 0.1f, tresh ) ); // Ankle position slightly forwarded return sampledToes; } public Vector3 GetSamplingHeelPoint( Transform foot, float tresh ) { Vector3 sampledHeel = foot.position; sampledHeel += foot.TransformDirection( _footLocalToGround ); // Ankle position offsetted to ground position sampledHeel -= foot.TransformDirection( _footForward ) * _footLocalToGround.magnitude * ( .6f - Mathf.Lerp( 0f, 2f, tresh ) ); // Ankle position slightly forwarded return sampledHeel; } #endregion } #endregion }